// Define packages:
var sigma = {};
sigma.tools = {};
sigma.classes = {};
sigma.instances = {};

// Adding Array helpers, if not present yet:
(function () {
	if (!Array.prototype.some) {
		Array.prototype.some = function (fun /*, thisp*/) {
			var len = this.length;
			if (typeof fun != 'function') {
				throw new TypeError();
			}

			var thisp = arguments[1];
			for (var i = 0; i < len; i++) {
				if (i in this &&
					fun.call(thisp, this[i], i, this)) {
					return true;
				}
			}

			return false;
		};
	}

	if (!Array.prototype.forEach) {
		Array.prototype.forEach = function (fun /*, thisp*/) {
			var len = this.length;
			if (typeof fun != 'function') {
				throw new TypeError();
			}

			var thisp = arguments[1];
			for (var i = 0; i < len; i++) {
				if (i in this) {
					fun.call(thisp, this[i], i, this);
				}
			}
		};
	}

	if (!Array.prototype.map) {
		Array.prototype.map = function (fun /*, thisp*/) {
			var len = this.length;
			if (typeof fun != 'function') {
				throw new TypeError();
			}

			var res = new Array(len);
			var thisp = arguments[1];
			for (var i = 0; i < len; i++) {
				if (i in this) {
					res[i] = fun.call(thisp, this[i], i, this);
				}
			}

			return res;
		};
	}

	if (!Array.prototype.filter) {
		Array.prototype.filter = function (fun /*, thisp*/) {
			var len = this.length;
			if (typeof fun != 'function')
				throw new TypeError();

			var res = new Array();
			var thisp = arguments[1];
			for (var i = 0; i < len; i++) {
				if (i in this) {
					var val = this[i]; // in case fun mutates this
					if (fun.call(thisp, val, i, this)) {
						res.push(val);
					}
				}
			}

			return res;
		};
	}

	if (!Object.keys) {
		Object.keys = (function () {
			var hasOwnProperty = Object.prototype.hasOwnProperty,
				hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
				dontEnums = [
				  'toString',
				  'toLocaleString',
				  'valueOf',
				  'hasOwnProperty',
				  'isPrototypeOf',
				  'propertyIsEnumerable',
				  'constructor'
				],
				dontEnumsLength = dontEnums.length;

			return function (obj) {
				if (typeof obj !== 'object' &&
					typeof obj !== 'function' ||
					obj === null
				) {
					throw new TypeError('Object.keys called on non-object');
				}

				var result = [];

				for (var prop in obj) {
					if (hasOwnProperty.call(obj, prop)) result.push(prop);
				}

				if (hasDontEnumBug) {
					for (var i = 0; i < dontEnumsLength; i++) {
						if (hasOwnProperty.call(obj, dontEnums[i])) {
							result.push(dontEnums[i]);
						}
					}
				}
				return result;
			}
		})();
	}
})();

/**
 * A jQuery like properties management class. It works like jQuery .css()
 * method: You can call it with juste one string to get the corresponding
 * property, with a string and anything else to set the corresponding property,
 * or directly with an object, and then each pair string / object (or any type)
 * will be set in the properties.
 * @constructor
 * @this {sigma.classes.Cascade}
 */
sigma.classes.Cascade = function () {
	/**
	 * This instance properties.
	 * @protected
	 * @type {Object}
	 */
	this.p = {};

	/**
	 * The method to use to set/get any property of this instance.
	 * @param  {(string|Object)} a1 If it is a string and if a2 is undefined,
	 *                              then it will return the corresponding
	 *                              property.
	 *                              If it is a string and if a2 is set, then it
	 *                              will set a2 as the property corresponding to
	 *                              a1, and return this.
	 *                              If it is an object, then each pair string /
	 *                              object (or any other type) will be set as a
	 *                              property.
	 * @param  {*?} a2              The new property corresponding to a1 if a1 is
	 *                              a string.
	 * @return {(*|sigma.classes.Cascade)} Returns itself or the corresponding
	 *                                     property.
	 */
	this.config = function (a1, a2) {
		if (typeof a1 == 'string' && a2 == undefined) {
			return this.p[a1];
		} else {
			var o = (typeof a1 == 'object' && a2 == undefined) ? a1 : {};
			if (typeof a1 == 'string') {
				o[a1] = a2;
			}

			for (var k in o) {
				if (this.p[k] != undefined) {
					this.p[k] = o[k];
				}
			}
			return this;
		}
	};
};

/**
 * sigma.js custom event dispatcher class.
 * @constructor
 * @this {sigma.classes.EventDispatcher}
 */
sigma.classes.EventDispatcher = function () {
	/**
	 * An object containing all the different handlers bound to one or many
	 * events, indexed by these events.
	 * @private
	 * @type {Object.<string,Object>}
	 */
	var _h = {};

	/**
	 * Represents "this", without the well-known scope issue.
	 * @private
	 * @type {sigma.classes.EventDispatcher}
	 */
	var _self = this;

	/**
	 * Will execute the handler the next (and only the next) time that the
	 * indicated event (or the indicated events) will be triggered.
	 * @param  {string} events            The name of the event (or the events
	 *                                    separated by spaces).
	 * @param  {function(Object)} handler The handler to bind.
	 * @return {sigma.classes.EventDispatcher} Returns itself.
	 */
	function one(events, handler) {
		if (!handler || !events) {
			return _self;
		}

		var eArray = ((typeof events) == 'string') ? events.split(' ') : events;

		eArray.forEach(function (event) {
			if (!_h[event]) {
				_h[event] = [];
			}

			_h[event].push({
				'h': handler,
				'one': true
			});
		});

		return _self;
	}

	/**
	 * Will execute the handler everytime that the indicated event (or the
	 * indicated events) will be triggered.
	 * @param  {string} events            The name of the event (or the events
	 *                                    separated by spaces).
	 * @param  {function(Object)} handler The handler to bind.
	 * @return {sigma.classes.EventDispatcher} Returns itself.
	 */
	function bind(events, handler) {
		if (!handler || !events) {
			return _self;
		}

		var eArray = ((typeof events) == 'string') ? events.split(' ') : events;

		eArray.forEach(function (event) {
			if (!_h[event]) {
				_h[event] = [];
			}

			_h[event].push({
				'h': handler,
				'one': false
			});
		});

		return _self;
	}

	/**
	 * Unbinds the handler from a specified event (or specified events).
	 * @param  {?string} events            The name of the event (or the events
	 *                                     separated by spaces). If undefined,
	 *                                     then all handlers are unbound.
	 * @param  {?function(Object)} handler The handler to unbind. If undefined,
	 *                                     each handler bound to the event or the
	 *                                     events will be unbound.
	 * @return {sigma.classes.EventDispatcher} Returns itself.
	 */
	function unbind(events, handler) {
		if (!events) {
			_h = {};
		}

		var eArray = typeof events == 'string' ? events.split(' ') : events;

		if (handler) {
			eArray.forEach(function (event) {
				if (_h[event]) {
					_h[event] = _h[event].filter(function (e) {
						return e['h'] != handler;
					});
				}

				if (_h[event] && _h[event].length == 0) {
					delete _h[event];
				}
			});
		} else {
			eArray.forEach(function (event) {
				delete _h[event];
			});
		}

		return _self;
	}

	/**
	 * Executes each handler bound to the event
	 * @param  {string} type     The type of the event.
	 * @param  {?Object} content The content of the event (optional).
	 * @return {sigma.classes.EventDispatcher} Returns itself.
	 */
	function dispatch(type, content) {
		if (_h[type]) {
			_h[type].forEach(function (e) {
				e['h']({
					'type': type,
					'content': content,
					'target': _self
				});
			});

			_h[type] = _h[type].filter(function (e) {
				return !e['one'];
			});
		}

		return _self;
	}

	/* PUBLIC INTERFACE: */
	this.one = one;
	this.bind = bind;
	this.unbind = unbind;
	this.dispatch = dispatch;
};

(function () {
	// Define local shortcut:
	var id = 0;

	// Define local package:
	var local = {};
	local.plugins = [];

	sigma.init = function (dom) {
		var inst = new Sigma(dom, (++id).toString());
		sigma.instances[id] = new SigmaPublic(inst);
		return sigma.instances[id];
	};

	/**
	 * The graph data model used in sigma.js.
	 * @constructor
	 * @extends sigma.classes.Cascade
	 * @extends sigma.classes.EventDispatcher
	 * @this {Graph}
	 */
	function Graph() {
		sigma.classes.Cascade.call(this);
		sigma.classes.EventDispatcher.call(this);

		/**
		 * Represents "this", without the well-known scope issue.
		 * @private
		 * @type {Graph}
		 */
		var self = this;

		/**
		 * The different parameters that determine how the nodes and edges should be
		 * translated and rescaled.
		 * @type {Object}
		 */
		this.p = {
			minNodeSize: 0,
			maxNodeSize: 0,
			minEdgeSize: 0,
			maxEdgeSize: 0,
			//   Scaling mode:
			//   - 'inside' (default)
			//   - 'outside'
			scalingMode: 'inside',
			nodesPowRatio: 0.5,
			edgesPowRatio: 0,
			sideMargin: 0,
			// Increasing / decreasing the following parameter will respectively make
			// arrows bigger / smaller relatively to their edges.
			arrowRatio: 3
		};

		/**
		 * Contains the borders of the graph. These are useful to avoid the user to
		 * drag the graph out of the canvas.
		 * @type {Object}
		 */
		this.borders = {};

		/**
		 * Inserts a node in the graph.
		 * @param {string} id     The node's ID.
		 * @param {object} params An object containing the different parameters
		 *                        of the node.
		 * @return {Graph} Returns itself.
		 */
		function addNode(id, params) {
			if (self.nodesIndex[id]) {
				throw new Error('Node "' + id + '" already exists.');
			}

			params = params || {};
			var n = {
				// Numbers :
				'x': 0,
				'y': 0,
				'size': 1,
				'degree': 0,
				'inDegree': 0,
				'outDegree': 0,
				// Flags :
				'fixed': false,
				'active': false,
				'hidden': false,
				'forceLabel': false,
				// Strings :
				'label': id.toString(),
				'id': id.toString(),
				// Custom attributes :
				'attr': {}
			};

			for (var k in params) {
				switch (k) {
					case 'id':
						break;
					case 'x':
					case 'y':
					case 'size':
						n[k] = +params[k];
						break;
					case 'fixed':
					case 'active':
					case 'hidden':
					case 'forceLabel':
						n[k] = !!params[k];
						break;
					case 'color':
					case 'label':
						n[k] = params[k];
						break;
					default:
						n['attr'][k] = params[k];
				}
			}

			self.nodes.push(n);
			self.nodesIndex[id.toString()] = n;

			return self;
		};

		/**
		 * Generates the clone of a node, to make it easier to be exported.
		 * @private
		 * @param  {Object} node The node to clone.
		 * @return {Object} The clone of the node.
		 */
		function cloneNode(node) {
			return {
				'x': node['x'],
				'y': node['y'],
				'size': node['size'],
				'degree': node['degree'],
				'inDegree': node['inDegree'],
				'outDegree': node['outDegree'],
				'displayX': node['displayX'],
				'displayY': node['displayY'],
				'displaySize': node['displaySize'],
				'label': node['label'],
				'id': node['id'],
				'color': node['color'],
				'fixed': node['fixed'],
				'active': node['active'],
				'hidden': node['hidden'],
				'forceLabel': node['forceLabel'],
				'attr': node['attr']
			};
		};

		/**
		 * Checks the clone of a node, and inserts its values when possible. For
		 * example, it is possible to modify the size or the color of a node, but it
		 * is not possible to modify its display values or its id.
		 * @private
		 * @param  {Object} node The original node.
		 * @param  {Object} copy The clone.
		 * @return {Graph} Returns itself.
		 */
		function checkNode(node, copy) {
			for (var k in copy) {
				switch (k) {
					case 'id':
					case 'attr':
					case 'degree':
					case 'inDegree':
					case 'outDegree':
					case 'displayX':
					case 'displayY':
					case 'displaySize':
						break;
					case 'x':
					case 'y':
					case 'size':
						node[k] = +copy[k];
						break;
					case 'fixed':
					case 'active':
					case 'hidden':
					case 'forceLabel':
						node[k] = !!copy[k];
						break;
					case 'color':
					case 'label':
						node[k] = (copy[k] || '').toString();
						break;
					default:
						node['attr'][k] = copy[k];
				}
			}

			return self;
		};

		/**
		 * Deletes one or several nodes from the graph, and the related edges.
		 * @param  {(string|Array.<string>)} v A string ID, or an Array of several
		 *                                     IDs.
		 * @return {Graph} Returns itself.
		 */
		function dropNode(v) {
			var a = (v instanceof Array ? v : [v]) || [];
			var nodesIdsToRemove = {};

			// Create hash to make lookups faster
			a.forEach(function (id) {
				if (self.nodesIndex[id]) {
					nodesIdsToRemove[id] = true;
				} else {
					sigma.log('Node "' + id + '" does not exist.');
				}
			});

			var indexesToRemove = [];
			self.nodes.forEach(function (n, i) {
				if (n['id'] in nodesIdsToRemove) {
					// Add to front, so we have a reverse-sorted list
					indexesToRemove.unshift(i);
					// No edges means we are done
					if (n['degree'] == 0) {
						delete nodesIdsToRemove[n['id']];
					}
				}
			});

			indexesToRemove.forEach(function (index) {
				self.nodes.splice(index, 1);
			});

			self.edges = self.edges.filter(function (e) {
				if (e['source']['id'] in nodesIdsToRemove) {
					delete self.edgesIndex[e['id']];
					e['target']['degree']--;
					e['target']['inDegree']--;
					return false;
				} else if (e['target']['id'] in nodesIdsToRemove) {
					delete self.edgesIndex[e['id']];
					e['source']['degree']--;
					e['source']['outDegree']--;
					return false;
				}
				return true;
			});

			return self;
		};

		/**
		 * Inserts an edge in the graph.
		 * @param {string} id     The edge ID.
		 * @param {string} source The ID of the edge source.
		 * @param {string} target The ID of the edge target.
		 * @param {object} params An object containing the different parameters
		 *                        of the edge.
		 * @return {Graph} Returns itself.
		 */
		function addEdge(id, source, target, params) {
			if (self.edgesIndex[id]) {
				throw new Error('Edge "' + id + '" already exists.');
			}

			if (!self.nodesIndex[source]) {
				var s = 'Edge\'s source "' + source + '" does not exist yet.';
				throw new Error(s);
			}

			if (!self.nodesIndex[target]) {
				var s = 'Edge\'s target "' + target + '" does not exist yet.';
				throw new Error(s);
			}

			params = params || {};
			var e = {
				'source': self.nodesIndex[source],
				'target': self.nodesIndex[target],
				'size': 1,
				'weight': 1,
				'displaySize': 0.5,
				'label': params['label'] || id.toString(),
				'id': id.toString(),
				'hidden': false,
				'attr': {}
			};

			e['source']['degree']++;
			e['source']['outDegree']++;
			e['target']['degree']++;
			e['target']['inDegree']++;

			for (var k in params) {
				switch (k) {
					case 'id':
					case 'source':
					case 'target':
						break;
					case 'hidden':
						e[k] = !!params[k];
						break;
					case 'size':
					case 'weight':
						e[k] = +params[k];
						break;
					case 'color':
					case 'arrow':
					case 'type':
						e[k] = params[k].toString();
						break;
					case 'label':
						e[k] = params[k];
						break;
					default:
						e['attr'][k] = params[k];
				}
			}

			self.edges.push(e);
			self.edgesIndex[id.toString()] = e;

			return self;
		};

		/**
		 * Generates the clone of a edge, to make it easier to be exported.
		 * @private
		 * @param  {Object} edge The edge to clone.
		 * @return {Object} The clone of the edge.
		 */
		function cloneEdge(edge) {
			return {
				'source': edge['source']['id'],
				'target': edge['target']['id'],
				'size': edge['size'],
				'type': edge['type'],
				'arrow': edge['arrow'],
				'weight': edge['weight'],
				'displaySize': edge['displaySize'],
				'label': edge['label'],
				'hidden': edge['hidden'],
				'id': edge['id'],
				'attr': edge['attr'],
				'color': edge['color']
			};
		};

		/**
		 * Checks the clone of an edge, and inserts its values when possible. For
		 * example, it is possible to modify the label or the type of an edge, but it
		 * is not possible to modify its display values or its id.
		 * @private
		 * @param  {Object} edge The original edge.
		 * @param  {Object} copy The clone.
		 * @return {Graph} Returns itself.
		 */
		function checkEdge(edge, copy) {
			for (var k in copy) {
				switch (k) {
					case 'id':
					case 'displaySize':
						break;
					case 'weight':
					case 'size':
						edge[k] = +copy[k];
						break;
					case 'source':
					case 'target':
						edge[k] = self.nodesIndex[k] || edge[k];
						break;
					case 'hidden':
						edge[k] = !!copy[k];
						break;
					case 'color':
					case 'label':
					case 'arrow':
					case 'type':
						edge[k] = (copy[k] || '').toString();
						break;
					default:
						edge['attr'][k] = copy[k];
				}
			}

			return self;
		};

		/**
		 * Deletes one or several edges from the graph.
		 * @param  {(string|Array.<string>)} v A string ID, or an Array of several
		 *                                     IDs.
		 * @return {Graph} Returns itself.
		 */
		function dropEdge(v) {
			var a = (v instanceof Array ? v : [v]) || [];

			a.forEach(function (id) {
				if (self.edgesIndex[id]) {
					self.edgesIndex[id]['source']['degree']--;
					self.edgesIndex[id]['source']['outDegree']--;
					self.edgesIndex[id]['target']['degree']--;
					self.edgesIndex[id]['target']['inDegree']--;

					var index = null;
					self.edges.some(function (n, i) {
						if (n['id'] == id) {
							index = i;
							return true;
						}
						return false;
					});

					index != null && self.edges.splice(index, 1);
					delete self.edgesIndex[id];
				} else {
					sigma.log('Edge "' + id + '" does not exist.');
				}
			});

			return self;
		};

		/**
		 * Deletes every nodes and edges from the graph.
		 * @return {Graph} Returns itself.
		 */
		function empty() {
			self.nodes = [];
			self.nodesIndex = {};
			self.edges = [];
			self.edgesIndex = {};

			return self;
		};

		/**
		 * Computes the display x, y and size of each node, relatively to the
		 * original values and the borders determined in the parameters, such as
		 * each node is in the described area.
		 * @param  {number} w           The area width (actually the width of the DOM
		 *                              root).
		 * @param  {number} h           The area height (actually the height of the
		 *                              DOM root).
		 * @param  {boolean} parseNodes Indicates if the nodes have to be parsed.
		 * @param  {boolean} parseEdges Indicates if the edges have to be parsed.
		 * @return {Graph} Returns itself.
		 */
		function rescale(w, h, parseNodes, parseEdges) {
			var weightMax = 0, sizeMax = 0;

			parseNodes && self.nodes.forEach(function (node) {
				sizeMax = Math.max(node['size'], sizeMax);
			});

			parseEdges && self.edges.forEach(function (edge) {
				weightMax = Math.max(edge['size'], weightMax);
			});

			sizeMax = sizeMax || 1;
			weightMax = weightMax || 1;

			// Recenter the nodes:
			var xMin, xMax, yMin, yMax;
			parseNodes && self.nodes.forEach(function (node) {
				xMax = Math.max(node['x'], xMax || node['x']);
				xMin = Math.min(node['x'], xMin || node['x']);
				yMax = Math.max(node['y'], yMax || node['y']);
				yMin = Math.min(node['y'], yMin || node['y']);
			});

			// First, we compute the scaling ratio, without considering the sizes
			// of the nodes : Each node will have its center in the canvas, but might
			// be partially out of it.
			var scale = self.p.scalingMode == 'outside' ?
						Math.max(w / Math.max(xMax - xMin, 1),
								 h / Math.max(yMax - yMin, 1)) :
						Math.min(w / Math.max(xMax - xMin, 1),
								 h / Math.max(yMax - yMin, 1));

			// Then, we correct that scaling ratio considering a margin, which is
			// basically the size of the biggest node.
			// This has to be done as a correction since to compare the size of the
			// biggest node to the X and Y values, we have to first get an
			// approximation of the scaling ratio.
			var margin = (self.p.maxNodeSize || sizeMax) / scale + self.p.sideMargin;
			xMax += margin;
			xMin -= margin;
			yMax += margin;
			yMin -= margin;

			scale = self.p.scalingMode == 'outside' ?
					Math.max(w / Math.max(xMax - xMin, 1),
							 h / Math.max(yMax - yMin, 1)) :
					Math.min(w / Math.max(xMax - xMin, 1),
							 h / Math.max(yMax - yMin, 1));

			// Size homothetic parameters:
			var a, b;
			if (!self.p.maxNodeSize && !self.p.minNodeSize) {
				a = 1;
				b = 0;
			} else if (self.p.maxNodeSize == self.p.minNodeSize) {
				a = 0;
				b = self.p.maxNodeSize;
			} else {
				a = (self.p.maxNodeSize - self.p.minNodeSize) / sizeMax;
				b = self.p.minNodeSize;
			}

			var c, d;
			if (!self.p.maxEdgeSize && !self.p.minEdgeSize) {
				c = 1;
				d = 0;
			} else if (self.p.maxEdgeSize == self.p.minEdgeSize) {
				c = 0;
				d = self.p.minEdgeSize;
			} else {
				c = (self.p.maxEdgeSize - self.p.minEdgeSize) / weightMax;
				d = self.p.minEdgeSize;
			}

			// Rescale the nodes:
			parseNodes && self.nodes.forEach(function (node) {
				node['displaySize'] = node['size'] * a + b;

				if (!node['fixed']) {
					node['displayX'] = (node['x'] - (xMax + xMin) / 2) * scale + w / 2;
					node['displayY'] = (node['y'] - (yMax + yMin) / 2) * scale + h / 2;
				}
			});

			parseEdges && self.edges.forEach(function (edge) {
				edge['displaySize'] = edge['size'] * c + d;
			});

			return self;
		};

		/**
		 * Translates the display values of the nodes and edges relatively to the
		 * scene position and zoom ratio.
		 * @param  {number} sceneX      The x position of the scene.
		 * @param  {number} sceneY      The y position of the scene.
		 * @param  {number} ratio       The zoom ratio of the scene.
		 * @param  {boolean} parseNodes Indicates if the nodes have to be parsed.
		 * @param  {boolean} parseEdges Indicates if the edges have to be parsed.
		 * @return {Graph} Returns itself.
		 */
		function translate(sceneX, sceneY, ratio, parseNodes, parseEdges) {
			var sizeRatio = Math.pow(ratio, self.p.nodesPowRatio);
			parseNodes && self.nodes.forEach(function (node) {
				if (!node['fixed']) {
					node['displayX'] = node['displayX'] * ratio + sceneX;
					node['displayY'] = node['displayY'] * ratio + sceneY;
				}

				node['displaySize'] = node['displaySize'] * sizeRatio;
			});

			parseEdges && self.edges.forEach(function (edge) {
				edge['displaySize'] =
				  edge['displaySize'] *
				  Math.pow(ratio, self.p.edgesPowRatio);

				edge['arrowDisplaySize'] =
				  edge['displaySize'] *
				  self.p.arrowRatio *
				  sizeRatio;
			});

			return self;
		};

		/**
		 * Determines the borders of the graph as it will be drawn. It is used to
		 * avoid the user to drag the graph out of the canvas.
		 */
		function setBorders() {
			self.borders = {};

			self.nodes.forEach(function (node) {
				self.borders.minX = Math.min(
				  self.borders.minX == undefined ?
					node['displayX'] - node['displaySize'] :
					self.borders.minX,
				  node['displayX'] - node['displaySize']
				);

				self.borders.maxX = Math.max(
				  self.borders.maxX == undefined ?
					node['displayX'] + node['displaySize'] :
					self.borders.maxX,
				  node['displayX'] + node['displaySize']
				);

				self.borders.minY = Math.min(
				  self.borders.minY == undefined ?
					node['displayY'] - node['displaySize'] :
					self.borders.minY,
				  node['displayY'] - node['displaySize']
				);

				self.borders.maxY = Math.max(
				  self.borders.maxY == undefined ?
					node['displayY'] - node['displaySize'] :
					self.borders.maxY,
				  node['displayY'] - node['displaySize']
				);
			});
		}

		/**
		 * Checks which nodes are under the (mX, mY) points, representing the mouse
		 * position.
		 * @param  {number} mX The mouse X position.
		 * @param  {number} mY The mouse Y position.
		 * @return {Graph} Returns itself.
		 */
		function checkHover(mX, mY) {
			var dX, dY, s, over = [], out = [];
			self.nodes.forEach(function (node) {
				if (node['hidden']) {
					node['hover'] = false;
					return;
				}

				dX = Math.abs(node['displayX'] - mX);
				dY = Math.abs(node['displayY'] - mY);
				s = node['displaySize'];

				var oldH = node['hover'];
				var newH = dX < s && dY < s && Math.sqrt(dX * dX + dY * dY) < s;

				if (oldH && !newH) {
					node['hover'] = false;
					out.push(node.id);
				} else if (newH && !oldH) {
					node['hover'] = true;
					over.push(node.id);
				}
			});

			over.length && self.dispatch('overnodes', over);
			out.length && self.dispatch('outnodes', out);

			return self;
		};

		/**
		 * Applies a function to a clone of each node (or indicated nodes), and then
		 * tries to apply the modifications made on the clones to the original nodes.
		 * @param  {function(Object)} fun The function to execute.
		 * @param  {?Array.<string>} ids  An Array of node IDs (optional).
		 * @return {Graph} Returns itself.
		 */
		function iterNodes(fun, ids) {
			var a = ids ? ids.map(function (id) {
				return self.nodesIndex[id];
			}) : self.nodes;

			var aCopies = a.map(cloneNode);
			aCopies.forEach(fun);

			a.forEach(function (n, i) {
				checkNode(n, aCopies[i]);
			});

			return self;
		};

		/**
		 * Applies a function to a clone of each edge (or indicated edges), and then
		 * tries to apply the modifications made on the clones to the original edges.
		 * @param  {function(Object)} fun The function to execute.
		 * @param  {?Array.<string>} ids  An Array of edge IDs (optional).
		 * @return {Graph} Returns itself.
		 */
		function iterEdges(fun, ids) {
			var a = ids ? ids.map(function (id) {
				return self.edgesIndex[id];
			}) : self.edges;

			var aCopies = a.map(cloneEdge);
			aCopies.forEach(fun);

			a.forEach(function (e, i) {
				checkEdge(e, aCopies[i]);
			});

			return self;
		};

		/**
		 * Returns a specific node clone or an array of specified node clones.
		 * @param  {(string|Array.<string>)} ids The ID or an array of node IDs.
		 * @return {(Object|Array.<Object>)} The clone or the array of clones.
		 */
		function getNodes(ids) {
			var a = ((ids instanceof Array ? ids : [ids]) || []).map(function (id) {
				return cloneNode(self.nodesIndex[id]);
			});

			return (ids instanceof Array ? a : a[0]);
		};

		/**
		 * Returns a specific edge clone or an array of specified edge clones.
		 * @param  {(string|Array.<string>)} ids The ID or an array of edge IDs.
		 * @return {(Object|Array.<Object>)} The clone or the array of clones.
		 */
		function getEdges(ids) {
			var a = ((ids instanceof Array ? ids : [ids]) || []).map(function (id) {
				return cloneEdge(self.edgesIndex[id]);
			});

			return (ids instanceof Array ? a : a[0]);
		};

		empty();

		this.addNode = addNode;
		this.addEdge = addEdge;
		this.dropNode = dropNode;
		this.dropEdge = dropEdge;

		this.iterEdges = iterEdges;
		this.iterNodes = iterNodes;

		this.getEdges = getEdges;
		this.getNodes = getNodes;

		this.empty = empty;
		this.rescale = rescale;
		this.translate = translate;
		this.setBorders = setBorders;
		this.checkHover = checkHover;
	}

	/**
	 * A class to monitor some local / global probes directly on an instance,
	 * inside a div DOM element.
	 * It executes different methods (called "probes") regularly, and displays
	 * the results on the element.
	 * @constructor
	 * @extends sigma.classes.Cascade
	 * @param {Sigma} instance The instance to monitor.
	 * @param {element} dom    The div DOM element to draw write on.
	 * @this {Monitor}
	 */
	function Monitor(instance, dom) {
		sigma.classes.Cascade.call(this);

		/**
		 * Represents "this", without the well-known scope issue.
		 * @private
		 * @type {Monitor}
		 */
		var self = this;

		/**
		 * {@link Sigma} instance owning this Monitor instance.
		 * @type {Sigma}
		 */
		this.instance = instance;

		/**
		 * Determines if the monitoring is activated or not.
		 * @type {Boolean}
		 */
		this.monitoring = false;

		/**
		 * The different parameters that define how this instance should work. It
		 * also contains the different probes.
		 * @see sigma.classes.Cascade
		 * @type {Object}
		 */
		this.p = {
			fps: 40,
			dom: dom,
			globalProbes: {
				'Time (ms)': sigma.chronos.getExecutionTime,
				'Queue': sigma.chronos.getQueuedTasksCount,
				'Tasks': sigma.chronos.getTasksCount,
				'FPS': sigma.chronos.getFPS
			},
			localProbes: {
				'Nodes count': function () { return self.instance.graph.nodes.length; },
				'Edges count': function () { return self.instance.graph.edges.length; }
			}
		};

		/**
		 * Activates the monitoring: Some texts describing some values about sigma.js
		 * or the owning {@link Sigma} instance will appear over the graph, but
		 * beneath the mouse sensible DOM element.
		 * @return {Monitor} Returns itself.
		 */
		function activate() {
			if (!self.monitoring) {
				self.monitoring = window.setInterval(routine, 1000 / self.p.fps);
			}

			return self;
		}

		/**
		 * Desactivates the monitoring: Will disappear, and stop computing the
		 * different probes.
		 * @return {Monitor} Returns itself.
		 */
		function desactivate() {
			if (self.monitoring) {
				window.clearInterval(self.monitoring);
				self.monitoring = null;

				self.p.dom.innerHTML = '';
			}

			return self;
		}

		/**
		 * The private method dedicated to compute the different values to observe.
		 * @private
		 * @return {Monitor} Returns itself.
		 */
		function routine() {
			var s = '';

			s += '<p>GLOBAL :</p>';
			for (var k in self.p.globalProbes) {
				s += '<p>' + k + ' : ' + self.p.globalProbes[k]() + '</p>';
			}

			s += '<br><p>LOCAL :</p>';
			for (var k in self.p.localProbes) {
				s += '<p>' + k + ' : ' + self.p.localProbes[k]() + '</p>';
			}

			self.p.dom.innerHTML = s;

			return self;
		}

		this.activate = activate;
		this.desactivate = desactivate;
	}

	/**
	 * This class listen to all the different mouse events, to normalize them and
	 * dispatch action events instead (from "startinterpolate" to "isdragging",
	 * etc).
	 * @constructor
	 * @extends sigma.classes.Cascade
	 * @extends sigma.classes.EventDispatcher
	 * @param {element} dom The DOM element to bind the handlers on.
	 * @this {MouseCaptor}
	 */
	function MouseCaptor(dom) {
		sigma.classes.Cascade.call(this);
		sigma.classes.EventDispatcher.call(this);

		/**
		 * Represents "this", without the well-known scope issue.
		 * @private
		 * @type {MouseCaptor}
		 */
		var self = this;

		/**
		 * The DOM element to bind the handlers on.
		 * @type {element}
		 */
		var dom = dom;

		/**
		 * The different parameters that define how this instance should work.
		 * @see sigma.classes.Cascade
		 * @type {Object}
		 */
		this.p = {
			minRatio: 1,
			maxRatio: 32,
			marginRatio: 1,
			zoomDelta: 0.1,
			dragDelta: 0.3,
			zoomMultiply: 2,
			directZooming: false,
			blockScroll: true,
			inertia: 1.1,
			mouseEnabled: true
		};

		var oldMouseX = 0;
		var oldMouseY = 0;
		var startX = 0;
		var startY = 0;

		var oldStageX = 0;
		var oldStageY = 0;
		var oldRatio = 1;

		var targetRatio = 1;
		var targetStageX = 0;
		var targetStageY = 0;

		var lastStageX = 0;
		var lastStageX2 = 0;
		var lastStageY = 0;
		var lastStageY2 = 0;

		var progress = 0;
		var isZooming = false;

		this.stageX = 0;
		this.stageY = 0;
		this.ratio = 1;

		this.mouseX = 0;
		this.mouseY = 0;

		this.isMouseDown = false;

		/**
		 * Extract the local X position from a mouse event.
		 * @private
		 * @param  {event} e A mouse event.
		 * @return {number} The local X value of the mouse.
		 */
		function getX(e) {
			return e.offsetX != undefined && e.offsetX ||
				   e.layerX != undefined && e.layerX ||
				   e.clientX != undefined && e.clientX;
		};

		/**
		 * Extract the local Y position from a mouse event.
		 * @private
		 * @param  {event} e A mouse event.
		 * @return {number} The local Y value of the mouse.
		 */
		function getY(e) {
			return e.offsetY != undefined && e.offsetY ||
				   e.layerY != undefined && e.layerY ||
				   e.clientY != undefined && e.clientY;
		};

		/**
		 * Extract the wheel delta from a mouse event.
		 * @private
		 * @param  {event} e A mouse event.
		 * @return {number} The wheel delta of the mouse.
		 */
		function getDelta(e) {
			return e.wheelDelta != undefined && e.wheelDelta ||
				   e.detail != undefined && -e.detail;
		};

		/**
		 * The handler listening to the 'move' mouse event. It will set the mouseX
		 * and mouseY values as the mouse position values, prevent the default event,
		 * and dispatch a 'move' event.
		 * @private
		 * @param  {event} event A 'move' mouse event.
		 */
		function moveHandler(event) {
			oldMouseX = self.mouseX;
			oldMouseY = self.mouseY;

			self.mouseX = getX(event);
			self.mouseY = getY(event);

			self.isMouseDown && drag(event);
			self.dispatch('move');

			if (event.preventDefault) {
				event.preventDefault();
			} else {
				event.returnValue = false;
			}
		};

		/**
		 * The handler listening to the 'up' mouse event. It will set the isMouseDown
		 * value as false, dispatch a 'mouseup' event, and trigger stopDrag().
		 * @private
		 * @param  {event} event A 'up' mouse event.
		 */
		function upHandler(event) {
			if (self.p.mouseEnabled && self.isMouseDown) {
				self.isMouseDown = false;
				self.dispatch('mouseup');
				stopDrag();

				if (event.preventDefault) {
					event.preventDefault();
				} else {
					event.returnValue = false;
				}
			}
		};

		/**
		 * The handler listening to the 'down' mouse event. It will set the
		 * isMouseDown value as true, dispatch a 'mousedown' event, and trigger
		 * startDrag().
		 * @private
		 * @param  {event} event A 'down' mouse event.
		 */
		function downHandler(event) {
			if (self.p.mouseEnabled) {
				self.isMouseDown = true;
				oldMouseX = self.mouseX;
				oldMouseY = self.mouseY;

				self.dispatch('mousedown');

				startDrag();

				if (event.preventDefault) {
					event.preventDefault();
				} else {
					event.returnValue = false;
				}
			}
		};

		/**
		 * The handler listening to the 'wheel' mouse event. It will trigger
		 * {@link startInterpolate} with the event delta as parameter.
		 * @private
		 * @param  {event} event A 'wheel' mouse event.
		 */
		function wheelHandler(event) {
			if (self.p.mouseEnabled) {
				startInterpolate(
				  self.mouseX,
				  self.mouseY,
				  self.ratio * (getDelta(event) > 0 ?
					self.p.zoomMultiply :
					1 / self.p.zoomMultiply)
				);

				if (self.p['blockScroll']) {
					if (event.preventDefault) {
						event.preventDefault();
					} else {
						event.returnValue = false;
					}
				}
			}
		};

		/**
		 * Will start computing the scene X and Y, until {@link stopDrag} is
		 * triggered.
		 */
		function startDrag() {
			oldStageX = self.stageX;
			oldStageY = self.stageY;
			startX = self.mouseX;
			startY = self.mouseY;

			lastStageX = self.stageX;
			lastStageX2 = self.stageX;
			lastStageY = self.stageY;
			lastStageY2 = self.stageY;

			self.dispatch('startdrag');
		};

		/**
		 * Stops computing the scene position.
		 */
		function stopDrag() {
			if (oldStageX != self.stageX || oldStageY != self.stageY) {
				startInterpolate(
				  self.stageX + self.p.inertia * (self.stageX - lastStageX2),
				  self.stageY + self.p.inertia * (self.stageY - lastStageY2)
				);
			}
		};

		/**
		 * Computes the position of the scene, relatively to the mouse position, and
		 * dispatches a "drag" event.
		 */
		function drag() {
			var newStageX = self.mouseX - startX + oldStageX;
			var newStageY = self.mouseY - startY + oldStageY;

			if (newStageX != self.stageX || newStageY != self.stageY) {
				lastStageX2 = lastStageX;
				lastStageY2 = lastStageY;

				lastStageX = newStageX;
				lastStageY = newStageY;

				self.stageX = newStageX;
				self.stageY = newStageY;
				self.dispatch('drag');
			}
		};

		/**
		 * Will start computing the scene zoom ratio, until {@link stopInterpolate} is
		 * triggered.
		 * @param {number} x     The new stage X.
		 * @param {number} y     The new stage Y.
		 * @param {number} ratio The new zoom ratio.
		 */
		function startInterpolate(x, y, ratio) {
			if (self.isMouseDown) {
				return;
			}

			window.clearInterval(self.interpolationID);
			isZooming = ratio != undefined;

			oldStageX = self.stageX;
			targetStageX = x;

			oldStageY = self.stageY;
			targetStageY = y;

			oldRatio = self.ratio;
			targetRatio = ratio || self.ratio;
			targetRatio = Math.min(
			  Math.max(targetRatio, self.p.minRatio),
			  self.p.maxRatio
			);

			progress =
			  self.p.directZooming ?
			  1 - (isZooming ? self.p.zoomDelta : self.p.dragDelta) :
			  0;

			if (
			  self.ratio != targetRatio ||
			  self.stageX != targetStageX ||
			  self.stageY != targetStageY
			) {
				interpolate();
				self.interpolationID = window.setInterval(interpolate, 50);
				self.dispatch('startinterpolate');
			}
		};

		/**
		 * Stops the move interpolation.
		 */
		function stopInterpolate() {
			var oldRatio = self.ratio;

			if (isZooming) {
				self.ratio = targetRatio;
				self.stageX = targetStageX +
							  (self.stageX - targetStageX) *
							  self.ratio /
							  oldRatio;
				self.stageY = targetStageY +
							  (self.stageY - targetStageY) *
							  self.ratio /
							  oldRatio;
			} else {
				self.stageX = targetStageX;
				self.stageY = targetStageY;
			}

			self.dispatch('stopinterpolate');
		};

		/**
		 * Computes the interpolate ratio and the position of the scene, relatively
		 * to the last mouse event delta received, and dispatches a "interpolate"
		 * event.
		 */
		function interpolate() {
			progress += (isZooming ? self.p.zoomDelta : self.p.dragDelta);
			progress = Math.min(progress, 1);

			var k = sigma.easing.quadratic.easeout(progress);
			var oldRatio = self.ratio;

			self.ratio = oldRatio * (1 - k) + targetRatio * k;

			if (isZooming) {
				self.stageX = targetStageX +
							  (self.stageX - targetStageX) *
							  self.ratio /
							  oldRatio;

				self.stageY = targetStageY +
							  (self.stageY - targetStageY) *
							  self.ratio /
							  oldRatio;
			} else {
				self.stageX = oldStageX * (1 - k) + targetStageX * k;
				self.stageY = oldStageY * (1 - k) + targetStageY * k;
			}

			self.dispatch('interpolate');
			if (progress >= 1) {
				window.clearInterval(self.interpolationID);
				stopInterpolate();
			}
		};

		/**
		 * Checks that there is always a part of the graph that is displayed, to
		 * avoid the user to drag the graph out of the stage.
		 * @param  {Object} b      An object containing the borders of the graph.
		 * @param  {number} width  The width of the stage.
		 * @param  {number} height The height of the stage.
		 * @return {MouseCaptor} Returns itself.
		 */
		function checkBorders(b, width, height) {
			// TODO : Find the good formula
			/*if (!isNaN(b.minX) && !isNaN(b.maxX)) {
			  self.stageX = Math.min(
				self.stageX = Math.max(
				  self.stageX,
				  (b.minX - width) * self.ratio +
					self.p.marginRatio*(b.maxX - b.minX)
				),
				(b.maxX - width) * self.ratio +
				  width -
				  self.p.marginRatio*(b.maxX - b.minX)
			  );
			}
		
			if (!isNaN(b.minY) && !isNaN(b.maxY)) {
			  self.stageY = Math.min(
				self.stageY = Math.max(
				  self.stageY,
				  (b.minY - height) * self.ratio +
					self.p.marginRatio*(b.maxY - b.minY)
				),
				(b.maxY - height) * self.ratio +
				  height -
				  self.p.marginRatio*(b.maxY - b.minY)
			  );
			}*/

			return self;
		};

		// ADD CALLBACKS
		dom.addEventListener('DOMMouseScroll', wheelHandler, true);
		dom.addEventListener('mousewheel', wheelHandler, true);
		dom.addEventListener('mousemove', moveHandler, true);
		dom.addEventListener('mousedown', downHandler, true);
		document.addEventListener('mouseup', upHandler, true);

		this.checkBorders = checkBorders;
		this.interpolate = startInterpolate;
	}

	/**
	 * This class draws the graph on the different canvas DOM elements. It just
	 * contains all the different methods to draw the graph, synchronously or
	 * pseudo-asynchronously.
	 * @constructor
	 * @param {CanvasRenderingContext2D} nodesCtx  Context dedicated to draw nodes.
	 * @param {CanvasRenderingContext2D} edgesCtx  Context dedicated to draw edges.
	 * @param {CanvasRenderingContext2D} labelsCtx Context dedicated to draw
	 *                                             labels.
	 * @param {CanvasRenderingContext2D} hoverCtx  Context dedicated to draw hover
	 *                                             nodes labels.
	 * @param {Graph} graph                        A reference to the graph to
	 *                                             draw.
	 * @param {number} w                           The width of the DOM root
	 *                                             element.
	 * @param {number} h                           The width of the DOM root
	 *                                             element.
	 * @extends sigma.classes.Cascade
	 * @this {Plotter}
	 */
	function Plotter(nodesCtx, edgesCtx, labelsCtx, hoverCtx, graph, w, h) {
		sigma.classes.Cascade.call(this);

		/**
		 * Represents "this", without the well-known scope issue.
		 * @private
		 * @type {Plotter}
		 */
		var self = this;

		/**
		 * The different parameters that define how this instance should work.
		 * @see sigma.classes.Cascade
		 * @type {Object}
		 */
		this.p = {
			// -------
			// LABELS:
			// -------
			//   Label color:
			//   - 'node'
			//   - default (then defaultLabelColor
			//              will be used instead)
			labelColor: 'default',
			defaultLabelColor: '#000',
			//   Label hover background color:
			//   - 'node'
			//   - default (then defaultHoverLabelBGColor
			//              will be used instead)
			labelHoverBGColor: 'default',
			defaultHoverLabelBGColor: '#fff',
			//   Label hover shadow:
			labelHoverShadow: true,
			labelHoverShadowColor: '#000',
			//   Label hover color:
			//   - 'node'
			//   - default (then defaultLabelHoverColor
			//              will be used instead)
			labelHoverColor: 'default',
			defaultLabelHoverColor: '#000',
			//   Label active background color:
			//   - 'node'
			//   - default (then defaultActiveLabelBGColor
			//              will be used instead)
			labelActiveBGColor: 'default',
			defaultActiveLabelBGColor: '#fff',
			//   Label active shadow:
			labelActiveShadow: true,
			labelActiveShadowColor: '#000',
			//   Label active color:
			//   - 'node'
			//   - default (then defaultLabelActiveColor
			//              will be used instead)
			labelActiveColor: 'default',
			defaultLabelActiveColor: '#000',
			//   Label size:
			//   - 'fixed'
			//   - 'proportional'
			//   Label size:
			//   - 'fixed'
			//   - 'proportional'
			labelSize: 'fixed',
			defaultLabelSize: 12, // for fixed display only
			labelSizeRatio: 2,    // for proportional display only
			labelThreshold: 6,
			font: 'Arial',
			hoverFont: '',
			activeFont: '',
			fontStyle: '',
			hoverFontStyle: '',
			activeFontStyle: '',
			// ------
			// EDGES:
			// ------
			//   Edge color:
			//   - 'source'
			//   - 'target'
			//   - default (then defaultEdgeColor or edge['color']
			//              will be used instead)
			edgeColor: 'source',
			defaultEdgeColor: '#aaa',
			defaultEdgeType: 'line',
			defaultEdgeArrow: 'none',
			// ------
			// NODES:
			// ------
			defaultNodeColor: '#aaa',
			// HOVER:
			//   Node hover color:
			//   - 'node'
			//   - default (then defaultNodeHoverColor
			//              will be used instead)
			nodeHoverColor: 'node',
			defaultNodeHoverColor: '#fff',
			// ACTIVE:
			//   Node active color:
			//   - 'node'
			//   - default (then defaultNodeActiveColor
			//              will be used instead)
			nodeActiveColor: 'node',
			defaultNodeActiveColor: '#fff',
			//   Node border color:
			//   - 'node'
			//   - default (then defaultNodeBorderColor
			//              will be used instead)
			borderSize: 0,
			nodeBorderColor: 'node',
			defaultNodeBorderColor: '#fff',
			// --------
			// PROCESS:
			// --------
			edgesSpeed: 200,
			nodesSpeed: 200,
			labelsSpeed: 200,
			// Added by Dimitroff
			defaultSelfClosingEdgeRadius: 40,
		};

		/**
		 * The canvas context dedicated to draw the nodes.
		 * @type {CanvasRenderingContext2D}
		 */
		var nodesCtx = nodesCtx;

		/**
		 * The canvas context dedicated to draw the edges.
		 * @type {CanvasRenderingContext2D}
		 */
		var edgesCtx = edgesCtx;

		/**
		 * The canvas context dedicated to draw the labels.
		 * @type {CanvasRenderingContext2D}
		 */
		var labelsCtx = labelsCtx;

		/**
		 * The canvas context dedicated to draw the hover nodes.
		 * @type {CanvasRenderingContext2D}
		 */
		var hoverCtx = hoverCtx;

		/**
		 * A reference to the graph to draw.
		 * @type {Graph}
		 */
		var graph = graph;

		/**
		 * The width of the stage to draw on.
		 * @type {number}
		 */
		var width = w;

		/**
		 * The height of the stage to draw on.
		 * @type {number}
		 */
		var height = h;

		/**
		 * The index of the next edge to draw.
		 * @type {number}
		 */
		this.currentEdgeIndex = 0;

		/**
		 * The index of the next node to draw.
		 * @type {number}
		 */
		this.currentNodeIndex = 0;

		/**
		 * The index of the next label to draw.
		 * @type {number}
		 */
		this.currentLabelIndex = 0;

		/**
		 * An atomic function to drawn the N next edges, with N as edgesSpeed.
		 * The counter is {@link this.currentEdgeIndex}.
		 * This function has been designed to work with {@link sigma.chronos}, that
		 * will insert frames at the middle of the calls, to make the edges drawing
		 * process fluid for the user.
		 * @see sigma.chronos
		 * @return {boolean} Returns true if all the edges are drawn and false else.
		 */
		function task_drawEdge() {
			var c = graph.edges.length;
			var s, t, i = 0;

			while (i++ < self.p.edgesSpeed && self.currentEdgeIndex < c) {
				e = graph.edges[self.currentEdgeIndex];
				s = e['source'];
				t = e['target'];
				if (e['hidden'] ||
					s['hidden'] ||
					t['hidden'] ||
					(!self.isOnScreen(s) && !self.isOnScreen(t))) {
					self.currentEdgeIndex++;
				} else {
					drawEdge(graph.edges[self.currentEdgeIndex++]);
				}
			}

			return self.currentEdgeIndex < c;
		};

		/**
		 * An atomic function to drawn the N next nodes, with N as nodesSpeed.
		 * The counter is {@link this.currentEdgeIndex}.
		 * This function has been designed to work with {@link sigma.chronos}, that
		 * will insert frames at the middle of the calls, to make the nodes drawing
		 * process fluid for the user.
		 * @see sigma.chronos
		 * @return {boolean} Returns true if all the nodes are drawn and false else.
		 */
		function task_drawNode() {
			var c = graph.nodes.length;
			var i = 0;

			while (i++ < self.p.nodesSpeed && self.currentNodeIndex < c) {
				if (!self.isOnScreen(graph.nodes[self.currentNodeIndex])) {
					self.currentNodeIndex++;
				} else {
					drawNode(graph.nodes[self.currentNodeIndex++]);
				}
			}

			return self.currentNodeIndex < c;
		};

		/**
		 * An atomic function to drawn the N next labels, with N as labelsSpeed.
		 * The counter is {@link this.currentEdgeIndex}.
		 * This function has been designed to work with {@link sigma.chronos}, that
		 * will insert frames at the middle of the calls, to make the labels drawing
		 * process fluid for the user.
		 * @see sigma.chronos
		 * @return {boolean} Returns true if all the labels are drawn and false else.
		 */
		function task_drawLabel() {
			var c = graph.nodes.length;
			var i = 0;

			while (i++ < self.p.labelsSpeed && self.currentLabelIndex < c) {
				if (!self.isOnScreen(graph.nodes[self.currentLabelIndex])) {
					self.currentLabelIndex++;
				} else {
					drawLabel(graph.nodes[self.currentLabelIndex++]);
				}
			}

			return self.currentLabelIndex < c;
		};

		/**
		 * Draws one node to the corresponding canvas.
		 * @param  {Object} node The node to draw.
		 * @return {Plotter} Returns itself.
		 */
		function drawNode(node) {
			var size = Math.round(node['displaySize'] * 10) / 10;
			var ctx = nodesCtx;

			ctx.fillStyle = node['color'];
			ctx.beginPath();
			ctx.arc(node['displayX'],
					node['displayY'],
					size,
					0,
					Math.PI * 2,
					true);

			ctx.closePath();
			ctx.fill();

			node['hover'] && drawHoverNode(node);
			return self;
		};

		/**
		 * Draws one edge to the corresponding canvas.
		 * @param  {Object} edge The edge to draw.
		 * @return {Plotter} Returns itself.
		 */
		function drawEdge(edge) {
			// Using array for coordinates so we can easily modify/return from
			// applyArrow():
			var sourceCoordinates = [
				  edge['source']['displayX'],
				  edge['source']['displayY']
			],
				targetCoordinates = [
				  edge['target']['displayX'],
				  edge['target']['displayY']
				],
				color = edge['color'];

			if (!color) {
				switch (self.p.edgeColor) {
					case 'source':
						color = edge['source']['color'] ||
								self.p.defaultNodeColor;
						break;
					case 'target':
						color = edge['target']['color'] ||
								self.p.defaultNodeColor;
						break;
					default:
						color = self.p.defaultEdgeColor;
						break;
				}
			}

			var ctx = edgesCtx;

			if (edge['source'] == edge['target']) {
				switch (edge['type'] || self.p.defaultEdgeType) {
					case 'curve':
						ctx.strokeStyle = color;
						// Magic numbers for teh win!11!!
						var radius = edge['radius'] || self.p.defaultSelfClosingEdgeRadius;

						ctx.save();
						ctx.translate(0, radius);
						ctx.beginPath();
						ctx.arc(sourceCoordinates[0], sourceCoordinates[1], radius, 0, 2 * Math.PI);
						ctx.stroke();

						if (edge['label']) {
							var x = sourceCoordinates[0];
							var fontHeight = parseInt(ctx.font.substr(0, 5).trim().replace("px", ""));
							var y = sourceCoordinates[1] + radius + fontHeight;
							ctx.fillStyle = color;
							ctx.fillText(edge['label'], x, y);
						}
						ctx.restore();

						break;
					default:
						throw new Error("Self-closing lines not supported atm");
				}
			}
			else {
				switch (edge['type'] || self.p.defaultEdgeType) {
					case 'curve':
						ctx.strokeStyle = color;

						var controlPointX =
							  (sourceCoordinates[0] + targetCoordinates[0]) / 2 +
							  (targetCoordinates[1] - sourceCoordinates[1]) / 4,
							controlPointY =
							  (sourceCoordinates[1] + targetCoordinates[1]) / 2 +
							  (sourceCoordinates[0] - targetCoordinates[0]) / 4;

						// Assignment is redundant here but makes it clear that this call
						// mutates sourceCoordinates:
						if (isArrowDrawRequired('source', edge['arrow'])) {
							sourceCoordinates = applyArrow(
							  ctx,
							  sourceCoordinates,
							  edge['source']['displaySize'],
							  controlPointX,
							  controlPointY,
							  edge['arrowDisplaySize']
							);
						}

						// Assignment is redundant here but makes it clear that this call
						// mutates targetCoordinates:
						if (isArrowDrawRequired('target', edge['arrow'])) {
							targetCoordinates = applyArrow(
							  ctx,
							  targetCoordinates,
							  edge['target']['displaySize'],
							  controlPointX,
							  controlPointY,
							  edge['arrowDisplaySize']
							);
						}

						ctx.lineWidth = edge['displaySize'] / 3;
						ctx.beginPath();
						ctx.moveTo(sourceCoordinates[0], sourceCoordinates[1]);
						ctx.quadraticCurveTo(controlPointX,
											 controlPointY,
											 targetCoordinates[0],
											 targetCoordinates[1]);

						ctx.stroke();
						if (edge['label']) {
							ctx.save();
							ctx.fillStyle = color;
							ctx.fillText(edge['label'], controlPointX, controlPointY);
							ctx.restore();
						}
						break;
					case 'line':
					default:
						ctx.strokeStyle = color;

						// Assignment is redundant here but makes it clear that this call
						// mutates sourceCoordinates:
						if (isArrowDrawRequired('source', edge['arrow'])) {
							sourceCoordinates = applyArrow(ctx,
							  sourceCoordinates,
							  edge['source']['displaySize'],
							  targetCoordinates[0],
							  targetCoordinates[1],
							  edge['arrowDisplaySize']
							);
						}

						// Assignment is redundant here but makes it clear that this call
						// mutates targetCoordinates:
						if (isArrowDrawRequired('target', edge['arrow'])) {
							targetCoordinates = applyArrow(ctx,
							  targetCoordinates,
							  edge['target']['displaySize'],
							  sourceCoordinates[0],
							  sourceCoordinates[1],
							  edge['arrowDisplaySize']
							);
						}

						ctx.lineWidth = edge['displaySize'] / 3;
						ctx.beginPath();
						ctx.moveTo(sourceCoordinates[0], sourceCoordinates[1]);
						ctx.lineTo(targetCoordinates[0], targetCoordinates[1]);

						ctx.stroke();

						if (edge['label']) {
							ctx.save();
							ctx.fillStyle = color;
							ctx.fillText(edge['label'], (sourceCoordinates[0] + targetCoordinates[0]) / 2, (sourceCoordinates[1] + targetCoordinates[1]) / 2);
							ctx.restore();
						}

						break;
				}
			}

			return self;
		};

		/**
		 * Draws one label to the corresponding canvas.
		 * @param  {Object} node The label to draw.
		 * @return {Plotter} Returns itself.
		 */
		function drawLabel(node) {
			var ctx = labelsCtx;

			if (node['displaySize'] >= self.p.labelThreshold || node['forceLabel']) {
				var fontSize = self.p.labelSize == 'fixed' ?
							   self.p.defaultLabelSize :
							   self.p.labelSizeRatio * node['displaySize'];

				ctx.font = self.p.fontStyle + fontSize + 'px ' + self.p.font;

				ctx.fillStyle = self.p.labelColor == 'node' ?
								(node['color'] || self.p.defaultNodeColor) :
								self.p.defaultLabelColor;
				ctx.fillText(
				  node['label'],
				  Math.round(node['displayX'] + node['displaySize'] * 1.5),
				  Math.round(node['displayY'] + fontSize / 2 - 3)
				);
			}

			return self;
		};

		/**
		 * Draws one hover node to the corresponding canvas.
		 * @param  {Object} node The hover node to draw.
		 * @return {Plotter} Returns itself.
		 */
		function drawHoverNode(node) {
			var ctx = hoverCtx;

			var fontSize = self.p.labelSize == 'fixed' ?
						   self.p.defaultLabelSize :
						   self.p.labelSizeRatio * node['displaySize'];

			ctx.font = (self.p.hoverFontStyle || self.p.fontStyle || '') + ' ' +
					   fontSize + 'px ' +
					   (self.p.hoverFont || self.p.font || '');

			ctx.fillStyle = self.p.labelHoverBGColor == 'node' ?
							(node['color'] || self.p.defaultNodeColor) :
							self.p.defaultHoverLabelBGColor;

			// Label background:
			ctx.beginPath();

			if (self.p.labelHoverShadow) {
				ctx.shadowOffsetX = 0;
				ctx.shadowOffsetY = 0;
				ctx.shadowBlur = 4;
				ctx.shadowColor = self.p.labelHoverShadowColor;
			}

			sigma.tools.drawRoundRect(
			  ctx,
			  Math.round(node['displayX'] - fontSize / 2 - 2),
			  Math.round(node['displayY'] - fontSize / 2 - 2),
			  Math.round(ctx.measureText(node['label']).width +
				node['displaySize'] * 1.5 +
				fontSize / 2 + 4),
			  Math.round(fontSize + 4),
			  Math.round(fontSize / 2 + 2),
			  'left'
			);
			ctx.closePath();
			ctx.fill();

			ctx.shadowOffsetX = 0;
			ctx.shadowOffsetY = 0;
			ctx.shadowBlur = 0;

			// Node border:
			ctx.beginPath();
			ctx.fillStyle = self.p.nodeBorderColor == 'node' ?
							(node['color'] || self.p.defaultNodeColor) :
							self.p.defaultNodeBorderColor;
			ctx.arc(Math.round(node['displayX']),
					Math.round(node['displayY']),
					node['displaySize'] + self.p.borderSize,
					0,
					Math.PI * 2,
					true);
			ctx.closePath();
			ctx.fill();

			// Node:
			ctx.beginPath();
			ctx.fillStyle = self.p.nodeHoverColor == 'node' ?
							(node['color'] || self.p.defaultNodeColor) :
							self.p.defaultNodeHoverColor;
			ctx.arc(Math.round(node['displayX']),
					Math.round(node['displayY']),
					node['displaySize'],
					0,
					Math.PI * 2,
					true);

			ctx.closePath();
			ctx.fill();

			// Label:
			ctx.fillStyle = self.p.labelHoverColor == 'node' ?
							(node['color'] || self.p.defaultNodeColor) :
							self.p.defaultLabelHoverColor;
			ctx.fillText(
			  node['label'],
			  Math.round(node['displayX'] + node['displaySize'] * 1.5),
			  Math.round(node['displayY'] + fontSize / 2 - 3)
			);

			return self;
		};

		/**
		 * Draws one active node to the corresponding canvas.
		 * @param  {Object} node The active node to draw.
		 * @return {Plotter} Returns itself.
		 */
		function drawActiveNode(node) {
			var ctx = hoverCtx;

			if (!isOnScreen(node)) {
				return self;
			}

			var fontSize = self.p.labelSize == 'fixed' ?
						   self.p.defaultLabelSize :
						   self.p.labelSizeRatio * node['displaySize'];

			ctx.font = (self.p.activeFontStyle || self.p.fontStyle || '') + ' ' +
					   fontSize + 'px ' +
					   (self.p.activeFont || self.p.font || '');

			ctx.fillStyle = self.p.labelHoverBGColor == 'node' ?
							(node['color'] || self.p.defaultNodeColor) :
							self.p.defaultActiveLabelBGColor;

			// Label background:
			ctx.beginPath();

			if (self.p.labelActiveShadow) {
				ctx.shadowOffsetX = 0;
				ctx.shadowOffsetY = 0;
				ctx.shadowBlur = 4;
				ctx.shadowColor = self.p.labelActiveShadowColor;
			}

			sigma.tools.drawRoundRect(
			  ctx,
			  Math.round(node['displayX'] - fontSize / 2 - 2),
			  Math.round(node['displayY'] - fontSize / 2 - 2),
			  Math.round(ctx.measureText(node['label']).width +
				node['displaySize'] * 1.5 +
				fontSize / 2 + 4),
			  Math.round(fontSize + 4),
			  Math.round(fontSize / 2 + 2),
			  'left'
			);
			ctx.closePath();
			ctx.fill();

			ctx.shadowOffsetX = 0;
			ctx.shadowOffsetY = 0;
			ctx.shadowBlur = 0;

			// Node border:
			ctx.beginPath();
			ctx.fillStyle = self.p.nodeBorderColor == 'node' ?
							(node['color'] || self.p.defaultNodeColor) :
							self.p.defaultNodeBorderColor;
			ctx.arc(Math.round(node['displayX']),
					Math.round(node['displayY']),
					node['displaySize'] + self.p.borderSize,
					0,
					Math.PI * 2,
					true);
			ctx.closePath();
			ctx.fill();

			// Node:
			ctx.beginPath();
			ctx.fillStyle = self.p.nodeActiveColor == 'node' ?
							(node['color'] || self.p.defaultNodeColor) :
							self.p.defaultNodeActiveColor;
			ctx.arc(Math.round(node['displayX']),
					Math.round(node['displayY']),
					node['displaySize'],
					0,
					Math.PI * 2,
					true);

			ctx.closePath();
			ctx.fill();

			// Label:
			ctx.fillStyle = self.p.labelActiveColor == 'node' ?
							(node['color'] || self.p.defaultNodeColor) :
							self.p.defaultLabelActiveColor;
			ctx.fillText(
			  node['label'],
			  Math.round(node['displayX'] + node['displaySize'] * 1.5),
			  Math.round(node['displayY'] + fontSize / 2 - 3)
			);

			return self;
		};

		/**
		 * Determines if a node is on the screen or not. The limits here are
		 * bigger than the actual screen, to avoid seeing labels disappear during
		 * the graph manipulation.
		 * @param  {Object}  node The node to check if it is on or out the screen.
		 * @return {boolean} Returns false if the node is hidden or not on the screen
		 *                   or true else.
		 */
		function isOnScreen(node) {
			if (isNaN(node['x']) || isNaN(node['y'])) {
				throw (new Error('A node\'s coordinate is not a ' +
								 'number (id: ' + node['id'] + ')')
				);
			}

			return !node['hidden'] &&
				   (node['displayX'] + node['displaySize'] > -width / 3) &&
				   (node['displayX'] - node['displaySize'] < width * 4 / 3) &&
				   (node['displayY'] + node['displaySize'] > -height / 3) &&
				   (node['displayY'] - node['displaySize'] < height * 4 / 3);
		};

		/**
		 * Resizes this instance.
		 * @param  {number} w The new width.
		 * @param  {number} h The new height.
		 * @return {Plotter} Returns itself.
		 */
		function resize(w, h) {
			width = w;
			height = h;

			return self;
		};

		/**
		 * Helper function that tells us if a 'source'/'target' arrow setting
		 * applies, based on active edge/plotter settings.
		 * @param  {string}  arrowheadLocation  'source' or 'target'.
		 * @param  {string}  edgeArrowSetting   arrow setting ('none', 'source',
		 *                                      'target', 'both') on the edge itself,
		 *                                      if any.
		 * @return {boolean} Returns true if draw is required for the passed-in
		 *                   arrowheadLocation.
		 */
		function isArrowDrawRequired(arrowheadLocation, edgeArrowSetting) {
			return (
			  edgeArrowSetting == arrowheadLocation ||
			  edgeArrowSetting == 'both' ||
			  (
				!edgeArrowSetting &&
				(
				  self.p.defaultEdgeArrow == arrowheadLocation ||
				  self.p.defaultEdgeArrow == 'both'
				)
			  )
			);
		};

		/**
		 * Helper function that draws an arrowhead at a node's border based on the
		 * node's center coordinates and size, as well as a set of "control"
		 * coordinates. Future implementations could add a nodeShape parm to this
		 * function.
		 * For efficiency, nodeCoordinates parm is mutated according to calculated
		 * arrowhead tip coordinates.
		 * @param {CanvasRenderingContext2D} ctx  The context within which to draw
		 *                                        arrows.
		 * @param {Array} nodeCoordinates         [x,y] coordinate of center of node.
		 *    THIS FUNCTION ADJUSTS THE COORDINATES IN THIS ARRAY TO MATCH THE
		 *    INTERSECTION BETWEEN THE NODE BORDER AND THE LINE FROM THE CENTER OF
		 *    THE NODE TO THE CONTROL COORDINATES.
		 *    Parm mutation here favors performance but may be considered bad form
		 *    in the general case.
		 * @param {number} nodeSize               The size of the node. For circle
		 *                                        nodes (the only supported node
		 *                                        shape as of 2013-05-14), size is
		 *                                        radius.
		 * @param {number} ctrlX                  x-coordinate of the control point.
		 * @param {number} ctrlY                  y-coordinate of the control point.
		 * @param {number} size                   length of arrowhead.
		 * @return {Array} Returns the mutated (arrowhead) coordinates.
		 */
		function applyArrow(ctx, nodeCoordinates, nodeSize, ctrlX, ctrlY, size) {
			// Calculate and re-assign edge connection coordinates (at node border
			// instead of node center):
			var xDiff = nodeCoordinates[0] - ctrlX;
			var yDiff = nodeCoordinates[1] - ctrlY;
			var ratio = nodeSize / Math.sqrt(xDiff * xDiff + yDiff * yDiff);

			nodeCoordinates[0] = nodeCoordinates[0] - xDiff * ratio;
			nodeCoordinates[1] = nodeCoordinates[1] - yDiff * ratio;

			// Draw arrowhead:
			ctx.lineWidth = 0;
			ctx.fillStyle = ctx.strokeStyle;
			sigma.tools.drawArrowhead(
			  ctx,
			  nodeCoordinates[0],
			  nodeCoordinates[1],
			  size,
			  sigma.tools.getIncidenceAngle(
				ctrlX,
				ctrlY,
				nodeCoordinates[0],
				nodeCoordinates[1]
			  )
			);

			return nodeCoordinates;
		};

		this.task_drawLabel = task_drawLabel;
		this.task_drawEdge = task_drawEdge;
		this.task_drawNode = task_drawNode;
		this.drawActiveNode = drawActiveNode;
		this.drawHoverNode = drawHoverNode;
		this.isOnScreen = isOnScreen;
		this.resize = resize;
	}
	/**
	 * Sigma is the main class. It represents the core of any instance id sigma.js.
	 * It is private and can be initialized only from inside sigma.js. To see its
	 * public interface, see {@link SigmaPublic}.
	 * It owns its own {@link Graph}, {@link MouseCaptor}, {@link Plotter}
	 * and {@link Monitor}.
	 * @constructor
	 * @extends sigma.classes.Cascade
	 * @extends sigma.classes.EventDispatcher
	 * @param {element} root The DOM root of this instance (a div, for example).
	 * @param {string} id    The ID of this instance.
	 * @this {Sigma}
	 */
	function Sigma(root, id) {
		sigma.classes.Cascade.call(this);
		sigma.classes.EventDispatcher.call(this);

		/**
		 * Represents "this", without the well-known scope issue.
		 * @private
		 * @type {Sigma}
		 */
		var self = this;

		/**
		 * The ID of the instance.
		 * @type {string}
		 */
		this.id = id.toString();

		/**
		 * The different parameters that define how this instance should work.
		 * @see sigma.classes.Cascade
		 * @type {Object}
		 */
		this.p = {
			auto: true,
			drawNodes: 2,
			drawEdges: 1,
			drawLabels: 2,
			lastNodes: 2,
			lastEdges: 0,
			lastLabels: 2,
			drawHoverNodes: true,
			drawActiveNodes: true
		};

		/**
		 * The root DOM element of this instance, containing every other elements.
		 * @type {element}
		 */
		this.domRoot = root;

		/**
		 * The width of this instance - initially, the root's width.
		 * @type {number}
		 */
		this.width = this.domRoot.offsetWidth;

		/**
		 * The height of this instance - initially, the root's height.
		 * @type {number}
		 */
		this.height = this.domRoot.offsetHeight;

		/**
		 * The graph of this instance - initiallyempty.
		 * @type {Graph}
		 */
		this.graph = new Graph();

		/**
		 * An object referencing every DOM elements used by this instance.
		 * @type {Object}
		 */
		this.domElements = {};

		initDOM('edges', 'canvas');
		initDOM('nodes', 'canvas');
		initDOM('labels', 'canvas');
		initDOM('hover', 'canvas');
		initDOM('monitor', 'div');
		initDOM('mouse', 'canvas');

		/**
		 * The class dedicated to manage the drawing process of the graph of the
		 * different canvas.
		 * @type {Plotter}
		 */
		this.plotter = new Plotter(
		  this.domElements.nodes.getContext('2d'),
		  this.domElements.edges.getContext('2d'),
		  this.domElements.labels.getContext('2d'),
		  this.domElements.hover.getContext('2d'),
		  this.graph,
		  this.width,
		  this.height
		);

		/**
		 * The class dedicated to monitor different probes about the running
		 * processes or the data, such as the number of nodes or edges, or how
		 * many times the graph is drawn per second.
		 * @type {Monitor}
		 */
		this.monitor = new Monitor(
		  this,
		  this.domElements.monitor
		);

		/**
		 * The class dedicated to manage the different mouse events.
		 * @type {MouseCaptor}
		 */
		this.mousecaptor = new MouseCaptor(
		  this.domElements.mouse,
		  this.id
		);

		// Interaction listeners:
		this.mousecaptor.bind('drag interpolate', function (e) {
			self.draw(
			  self.p.auto ? 2 : self.p.drawNodes,
			  self.p.auto ? 0 : self.p.drawEdges,
			  self.p.auto ? 2 : self.p.drawLabels,
			  true
			);
		}).bind('stopdrag stopinterpolate', function (e) {
			self.draw(
			  self.p.auto ? 2 : self.p.drawNodes,
			  self.p.auto ? 1 : self.p.drawEdges,
			  self.p.auto ? 2 : self.p.drawLabels,
			  true
			);
		}).bind('mousedown mouseup', function (e) {
			var targeted = self.graph.nodes.filter(function (n) {
				return !!n['hover'];
			}).map(function (n) {
				return n.id;
			});

			self.dispatch(
			  e['type'] == 'mousedown' ?
				'downgraph' :
				'upgraph'
			);

			if (targeted.length) {
				self.dispatch(
				  e['type'] == 'mousedown' ?
					'downnodes' :
					'upnodes',
				  targeted
				);
			}
		}).bind('move', function () {
			self.domElements.hover.getContext('2d').clearRect(
			  0,
			  0,
			  self.domElements.hover.width,
			  self.domElements.hover.height
			);

			drawHover();
			drawActive();
		});

		sigma.chronos.bind('startgenerators', function () {
			if (sigma.chronos.getGeneratorsIDs().some(function (id) {
			  return !!id.match(new RegExp('_ext_' + self.id + '$', ''));
			})) {
				self.draw(
				  self.p.auto ? 2 : self.p.drawNodes,
				  self.p.auto ? 0 : self.p.drawEdges,
				  self.p.auto ? 2 : self.p.drawLabels
				);
			}
		}).bind('stopgenerators', function () {
			self.draw();
		});

		/**
		 * Resizes the element, and redraws the graph with the last settings.
		 * @param  {?number} w The new width (if undefined, it will use the root
		 *                     width).
		 * @param  {?number} h The new height (if undefined, it will use the root
		 *                     height).
		 * @return {Sigma} Returns itself.
		 */
		function resize(w, h) {
			var oldW = self.width, oldH = self.height;

			if (w != undefined && h != undefined) {
				self.width = w;
				self.height = h;
			} else {
				self.width = self.domRoot.offsetWidth;
				self.height = self.domRoot.offsetHeight;
			}

			if (oldW != self.width || oldH != self.height) {
				for (var k in self.domElements) {
					self.domElements[k].setAttribute('width', self.width + 'px');
					self.domElements[k].setAttribute('height', self.height + 'px');
				}

				self.plotter.resize(self.width, self.height);

				self.draw(
				  self.p.lastNodes,
				  self.p.lastEdges,
				  self.p.lastLabels,
				  true
				);
			}
			return self;
		};

		/**
		 * Kills every drawing task currently running. Basically, it stops this
		 * instance's drawing process.
		 * @return {Sigma} Returns itself.
		 */
		function clearSchedule() {
			sigma.chronos.removeTask(
			  'node_' + self.id, 2
			).removeTask(
			  'edge_' + self.id, 2
			).removeTask(
			  'label_' + self.id, 2
			).stopTasks();
			return self;
		};

		/**
		 * Initialize a DOM element, that will be stores by this instance, to make
		 * automatic these elements resizing.
		 * @private
		 * @param  {string} id   The element's ID.
		 * @param  {string} type The element's nodeName (Example : canvas, div, ...).
		 * @return {Sigma} Returns itself.
		 */
		function initDOM(id, type) {
			self.domElements[id] = document.createElement(type);
			self.domElements[id].style.position = 'absolute';
			self.domElements[id].setAttribute('id', 'sigma_' + id + '_' + self.id);
			self.domElements[id].setAttribute('class', 'sigma_' + id + '_' + type);
			self.domElements[id].setAttribute('width', self.width + 'px');
			self.domElements[id].setAttribute('height', self.height + 'px');

			self.domRoot.appendChild(self.domElements[id]);
			return self;
		};

		/**
		 * Starts the graph drawing process. The three first parameters indicate
		 * how the different layers have to be drawn:
		 * . -1: The layer is not drawn, but it is not erased.
		 * . 0:  The layer is not drawn.
		 * . 1:  The layer is drawn progressively.
		 * . 2:  The layer is drawn directly.
		 * @param  {?number} nodes  Determines if and how the nodes must be drawn.
		 * @param  {?number} edges  Determines if and how the edges must be drawn.
		 * @param  {?number} labels Determines if and how the labels must be drawn.
		 * @param  {?boolean} safe  If true, nothing will happen if any generator
		 *                          affiliated to this instance is currently running
		 *                          (an iterative layout, for example).
		 * @return {Sigma} Returns itself.
		 */
		function draw(nodes, edges, labels, safe) {
			if (safe && sigma.chronos.getGeneratorsIDs().some(function (id) {
			  return !!id.match(new RegExp('_ext_' + self.id + '$', ''));
			})) {
				return self;
			}

			var n = (nodes == undefined) ? self.p.drawNodes : nodes;
			var e = (edges == undefined) ? self.p.drawEdges : edges;
			var l = (labels == undefined) ? self.p.drawLabels : labels;

			var params = {
				nodes: n,
				edges: e,
				labels: l
			};

			self.p.lastNodes = n;
			self.p.lastEdges = e;
			self.p.lastLabels = l;

			// Remove tasks:
			clearSchedule();

			// Rescale graph:
			self.graph.rescale(
			  self.width,
			  self.height,
			  n > 0,
			  e > 0
			).setBorders();

			self.mousecaptor.checkBorders(
			  self.graph.borders,
			  self.width,
			  self.height
			);

			self.graph.translate(
			  self.mousecaptor.stageX,
			  self.mousecaptor.stageY,
			  self.mousecaptor.ratio,
			  n > 0,
			  e > 0
			);

			self.dispatch(
			  'graphscaled'
			);

			// Clear scene:
			for (var k in self.domElements) {
				if (
				  self.domElements[k].nodeName.toLowerCase() == 'canvas' &&
				  (params[k] == undefined || params[k] >= 0)
				) {
					self.domElements[k].getContext('2d').clearRect(
					  0,
					  0,
					  self.domElements[k].width,
					  self.domElements[k].height
					);
				}
			}

			self.plotter.currentEdgeIndex = 0;
			self.plotter.currentNodeIndex = 0;
			self.plotter.currentLabelIndex = 0;

			var previous = null;
			var start = false;

			if (n) {
				if (n > 1) {
					while (self.plotter.task_drawNode()) { }
				} else {
					sigma.chronos.addTask(
					  self.plotter.task_drawNode,
					  'node_' + self.id,
					  false
					);

					start = true;
					previous = 'node_' + self.id;
				}
			}

			if (l) {
				if (l > 1) {
					while (self.plotter.task_drawLabel()) { }
				} else {
					if (previous) {
						sigma.chronos.queueTask(
						  self.plotter.task_drawLabel,
						  'label_' + self.id,
						  previous
						);
					} else {
						sigma.chronos.addTask(
						  self.plotter.task_drawLabel,
						  'label_' + self.id,
						  false
						);
					}

					start = true;
					previous = 'label_' + self.id;
				}
			}

			if (e) {
				if (e > 1) {
					while (self.plotter.task_drawEdge()) { }
				} else {
					if (previous) {
						sigma.chronos.queueTask(
						  self.plotter.task_drawEdge,
						  'edge_' + self.id,
						  previous
						);
					} else {
						sigma.chronos.addTask(
						  self.plotter.task_drawEdge,
						  'edge_' + self.id,
						  false
						);
					}

					start = true;
					previous = 'edge_' + self.id;
				}
			}

			self.dispatch(
			  'draw'
			);

			self.refresh();

			start && sigma.chronos.runTasks();
			return self;
		};

		/**
		 * Draws the hover and active nodes labels.
		 * @return {Sigma} Returns itself.
		 */
		function refresh() {
			self.domElements.hover.getContext('2d').clearRect(
			  0,
			  0,
			  self.domElements.hover.width,
			  self.domElements.hover.height
			);

			drawHover();
			drawActive();

			return self;
		}

		/**
		 * Draws the hover nodes labels. This method is applied directly, and does
		 * not use the pseudo-asynchronous tasks process.
		 * @return {Sigma} Returns itself.
		 */
		function drawHover() {
			if (self.p.drawHoverNodes) {
				self.graph.checkHover(
				  self.mousecaptor.mouseX,
				  self.mousecaptor.mouseY
				);

				self.graph.nodes.forEach(function (node) {
					if (node.hover && !node.active) {
						self.plotter.drawHoverNode(node);
					}
				});
			}

			return self;
		}

		/**
		 * Draws the active nodes labels. This method is applied directly, and does
		 * not use the pseudo-asynchronous tasks process.
		 * @return {Sigma} Returns itself.
		 */
		function drawActive() {
			if (self.p.drawActiveNodes) {
				self.graph.nodes.forEach(function (node) {
					if (node.active) {
						self.plotter.drawActiveNode(node);
					}
				});
			}

			return self;
		}

		// Apply plugins:
		for (var i = 0; i < local.plugins.length; i++) {
			local.plugins[i](this);
		}

		this.draw = draw;
		this.resize = resize;
		this.refresh = refresh;
		this.drawHover = drawHover;
		this.drawActive = drawActive;
		this.clearSchedule = clearSchedule;

		window.addEventListener('resize', function () {
			self.resize();
		});
	}

	function SigmaPublic(sigmaInstance) {
		var s = sigmaInstance;
		var self = this;
		sigma.classes.EventDispatcher.call(this);

		this._core = sigmaInstance;

		this.kill = function () {
			// TODO
		};

		this.getID = function () {
			return s.id;
		};

		// Config:
		this.configProperties = function (a1, a2) {
			var res = s.config(a1, a2);
			return res == s ? self : res;
		};

		this.drawingProperties = function (a1, a2) {
			var res = s.plotter.config(a1, a2);
			return res == s.plotter ? self : res;
		};

		this.mouseProperties = function (a1, a2) {
			var res = s.mousecaptor.config(a1, a2);
			return res == s.mousecaptor ? self : res;
		};

		this.graphProperties = function (a1, a2) {
			var res = s.graph.config(a1, a2);
			return res == s.graph ? self : res;
		};

		this.getMouse = function () {
			return {
				mouseX: s.mousecaptor.mouseX,
				mouseY: s.mousecaptor.mouseY,
				down: s.mousecaptor.isMouseDown
			};
		};

		// Actions:
		this.position = function (stageX, stageY, ratio) {
			if (arguments.length == 0) {
				return {
					stageX: s.mousecaptor.stageX,
					stageY: s.mousecaptor.stageY,
					ratio: s.mousecaptor.ratio
				};
			} else {
				s.mousecaptor.stageX = stageX != undefined ?
					stageX :
				  s.mousecaptor.stageX;
				s.mousecaptor.stageY = stageY != undefined ?
					stageY :
				  s.mousecaptor.stageY;
				s.mousecaptor.ratio = ratio != undefined ?
					ratio :
				  s.mousecaptor.ratio;

				return self;
			}
		};

		this.goTo = function (stageX, stageY, ratio) {
			s.mousecaptor.interpolate(stageX, stageY, ratio);
			return self;
		};

		this.zoomTo = function (x, y, ratio) {
			ratio = Math.min(
					  Math.max(s.mousecaptor.config('minRatio'), ratio),
					  s.mousecaptor.config('maxRatio')
					);
			if (ratio == s.mousecaptor.ratio) {
				s.mousecaptor.interpolate(
				  x - s.width / 2 + s.mousecaptor.stageX,
				  y - s.height / 2 + s.mousecaptor.stageY
				);
			} else {
				s.mousecaptor.interpolate(
				  (ratio * x - s.mousecaptor.ratio * s.width / 2) /
				  (ratio - s.mousecaptor.ratio),
				  (ratio * y - s.mousecaptor.ratio * s.height / 2) /
				  (ratio - s.mousecaptor.ratio),
				  ratio
				);
			}
			return self;
		};

		this.resize = function (w, h) {
			s.resize(w, h);
			return self;
		};

		this.draw = function (nodes, edges, labels, safe) {
			s.draw(nodes, edges, labels, safe);
			return self;
		};

		this.refresh = function () {
			s.refresh();
			return self;
		};

		// Tasks methods:
		this.addGenerator = function (id, task, condition) {
			sigma.chronos.addGenerator(id + '_ext_' + s.id, task, condition);
			return self;
		};

		this.removeGenerator = function (id) {
			sigma.chronos.removeGenerator(id + '_ext_' + s.id);
			return self;
		};

		// Graph methods:
		this.addNode = function (id, params) {
			s.graph.addNode(id, params);
			return self;
		};

		this.addEdge = function (id, source, target, params) {
			s.graph.addEdge(id, source, target, params);
			return self;
		}

		this.dropNode = function (v) {
			s.graph.dropNode(v);
			return self;
		};

		this.dropEdge = function (v) {
			s.graph.dropEdge(v);
			return self;
		};

		this.pushGraph = function (object, safe) {
			object.nodes && object.nodes.forEach(function (node) {
				node['id'] && (!safe || !s.graph.nodesIndex[node['id']]) &&
							  self.addNode(node['id'], node);
			});

			var isEdgeValid;
			object.edges && object.edges.forEach(function (edge) {
				validID = edge['source'] && edge['target'] && edge['id'];
				validID &&
				  (!safe || !s.graph.edgesIndex[edge['id']]) &&
				  self.addEdge(
					edge['id'],
					edge['source'],
					edge['target'],
					edge
				  );
			});

			return self;
		};

		this.emptyGraph = function () {
			s.graph.empty();
			return self;
		};

		this.getNodesCount = function () {
			return s.graph.nodes.length;
		};

		this.getEdgesCount = function () {
			return s.graph.edges.length;
		};

		this.iterNodes = function (fun, ids) {
			s.graph.iterNodes(fun, ids);
			return self;
		};

		this.iterEdges = function (fun, ids) {
			s.graph.iterEdges(fun, ids);
			return self;
		};

		this.getNodes = function (ids) {
			return s.graph.getNodes(ids);
		};

		this.getEdges = function (ids) {
			return s.graph.getEdges(ids);
		};

		// Monitoring
		this.activateMonitoring = function () {
			return s.monitor.activate();
		};

		this.desactivateMonitoring = function () {
			return s.monitor.desactivate();
		};

		// Events
		s.bind('downnodes upnodes downgraph upgraph', function (e) {
			self.dispatch(e.type, e.content);
		});

		s.graph.bind('overnodes outnodes', function (e) {
			self.dispatch(e.type, e.content);
		});
	}

	/**
	 * Add a function to the prototype of SigmaPublic, but with access to the
	 * Sigma class properties.
	 * @param {string} pluginName        [description].
	 * @param {function} caller          [description].
	 * @param {function(Sigma)} launcher [description].
	 */
	sigma.addPlugin = function (pluginName, caller, launcher) {
		SigmaPublic.prototype[pluginName] = caller;
		local.plugins.push(launcher);
	};
	/**
	 * sigma.chronos manages frames insertion to simulate asynchronous computing.
	 * It has been designed to make possible to execute heavy computing tasks
	 * for the browser, without freezing it.
	 * @constructor
	 * @extends sigma.classes.Cascade
	 * @extends sigma.classes.EventDispatcher
	 * @this {sigma.chronos}
	 */
	sigma.chronos = new (function () {
		sigma.classes.EventDispatcher.call(this);

		/**
		 * Represents "this", without the well-known scope issue.
		 * @private
		 * @type {sigma.chronos}
		 */
		var self = this;

		/**
		 * Indicates whether any task is actively running or not.
		 * @private
		 * @type {boolean}
		 */
		var isRunning = false;

		/**
		 * Indicates the FPS "goal", that will define the theoretical
		 * frame length.
		 * @private
		 * @type {number}
		 */
		var fpsReq = 80;

		/**
		 * Stores the last computed FPS value (FPS is computed only when any
		 * task is running).
		 * @private
		 * @type {number}
		 */
		var lastFPS = 0;

		/**
		 * The number of frames inserted since the last start.
		 * @private
		 * @type {number}
		 */
		var framesCount = 0;

		/**
		 * The theoretical frame time.
		 * @private
		 * @type {number}
		 */
		var frameTime = 1000 / fpsReq;

		/**
		 * The theoretical frame length, minus the last measured delay.
		 * @private
		 * @type {number}
		 */
		var correctedFrameTime = frameTime;

		/**
		 * The measured length of the last frame.
		 * @private
		 * @type {number}
		 */
		var effectiveTime = 0;

		/**
		 * The time passed since the last runTasks action.
		 * @private
		 * @type {number}
		 */
		var currentTime = 0;

		/**
		 * The time when the last frame was inserted.
		 * @private
		 * @type {number}
		 */
		var startTime = 0;

		/**
		 * The difference between the theoretical frame length and the
		 * last measured frame length.
		 * @private
		 * @type {number}
		 */
		var delay = 0;

		/**
		 * The container of all active generators.
		 * @private
		 * @type {Object.<string, Object>}
		 */
		var generators = {};

		/**
		 * The array of all the referenced and active tasks.
		 * @private
		 * @type {Array.<Object>}
		 */
		var tasks = [];

		/**
		 * The array of all the referenced and queued tasks.
		 * @private
		 * @type {Array.<Object>}
		 */
		var queuedTasks = [];

		/**
		 * The index of the next task to execute.
		 * @private
		 * @type {number}
		 */
		var taskIndex = 0;


		/**
		 * Inserts a frame before executing the callback.
		 * @param  {function()} callback The callback to execute after having
		 *                               inserted the frame.
		 * @return {sigma.chronos} Returns itself.
		 */
		function insertFrame(callback) {
			window.setTimeout(callback, 0);
			return self;
		}

		/**
		 * The local method that executes routine, and inserts frames when needed.
		 * It dispatches a "frameinserted" event after having inserted any frame,
		 * and an "insertframe" event before.
		 * @private
		 */
		function frameInserter() {
			self.dispatch('frameinserted');
			while (isRunning && tasks.length && routine()) { }

			if (!isRunning || !tasks.length) {
				stopTasks();
			} else {
				startTime = (new Date()).getTime();
				framesCount++;
				delay = effectiveTime - frameTime;
				correctedFrameTime = frameTime - delay;

				self.dispatch('insertframe');
				insertFrame(frameInserter);
			}
		};

		/**
		 * The local method that executes the tasks, and compares the current frame
		 * length to the ideal frame length.
		 * @private
		 * @return {boolean} Returns false if the current frame should be ended,
		 *                   and true else.
		 */
		function routine() {
			taskIndex = taskIndex % tasks.length;

			if (!tasks[taskIndex].task()) {
				var n = tasks[taskIndex].taskName;

				queuedTasks = queuedTasks.filter(function (e) {
					(e.taskParent == n) && tasks.push({
						taskName: e.taskName,
						task: e.task
					});
					return e.taskParent != n;
				});

				self.dispatch('killed', tasks.splice(taskIndex--, 1)[0]);
			}

			taskIndex++;
			effectiveTime = (new Date()).getTime() - startTime;
			return effectiveTime <= correctedFrameTime;
		};

		/**
		 * Starts tasks execution.
		 * @return {sigma.chronos} Returns itself.
		 */
		function runTasks() {
			isRunning = true;
			taskIndex = 0;
			framesCount = 0;

			startTime = (new Date()).getTime();
			currentTime = startTime;

			self.dispatch('start');
			self.dispatch('insertframe');
			insertFrame(frameInserter);
			return self;
		};

		/**
		 * Stops tasks execution, and dispatch a "stop" event.
		 * @return {sigma.chronos} Returns itself.
		 */
		function stopTasks() {
			self.dispatch('stop');
			isRunning = false;
			return self;
		};

		/**
		 * A task is a function that will be executed continuously while it returns
		 * true. As soon as it return false, the task will be removed.
		 * If several tasks are present, they will be executed in parallele.
		 * This method will add the task to this execution process.
		 * @param {function(): boolean} task     The task to add.
		 * @param {string} name                  The name of the worker, used for
		 *                                       managing the different tasks.
		 * @param {boolean} autostart            If true, sigma.chronos will start
		 *                                       automatically if it is not working
		 *                                       yet.
		 * @return {sigma.chronos} Returns itself.
		 */
		function addTask(task, name, autostart) {
			if (typeof task != 'function') {
				throw new Error('Task "' + name + '" is not a function');
			}

			tasks.push({
				taskName: name,
				task: task
			});

			isRunning = !!(isRunning || (autostart && runTasks()) || true);
			return self;
		};

		/**
		 * Will add a task that will be start to be executed as soon as a task
		 * named as the parent will be removed.
		 * @param {function(): boolean} task     The task to add.
		 * @param {string} name                  The name of the worker, used for
		 *                                       managing the different tasks.
		 * @param {string} parent                The name of the parent task.
		 * @return {sigma.chronos} Returns itself.
		 */
		function queueTask(task, name, parent) {
			if (typeof task != 'function') {
				throw new Error('Task "' + name + '" is not a function');
			}

			if (!tasks.concat(queuedTasks).some(function (e) {
			  return e.taskName == parent;
			})) {
				throw new Error(
				  'Parent task "' + parent + '" of "' + name + '" is not attached.'
				);
			}

			queuedTasks.push({
				taskParent: parent,
				taskName: name,
				task: task
			});

			return self;
		};

		/**
		 * Removes a task.
		 * @param  {string} v           If v is undefined, then every tasks will
		 *                              be removed. If not, each task named v will
		 *                              be removed.
		 * @param  {number} queueStatus Determines the queued tasks behaviour. If 0,
		 *                              then nothing will happen. If 1, the tasks
		 *                              queued to any removed task will be triggered.
		 *                              If 2, the tasks queued to any removed task
		 *                              will be removed as well.
		 * @return {sigma.chronos} Returns itself.
		 */
		function removeTask(v, queueStatus) {
			if (v == undefined) {
				tasks = [];
				if (queueStatus == 1) {
					queuedTasks = [];
				} else if (queueStatus == 2) {
					tasks = queuedTasks;
					queuedTasks = [];
				}
				stopTasks();
			} else {
				var n = (typeof v == 'string') ? v : '';
				tasks = tasks.filter(function (e) {
					if ((typeof v == 'string') ? e.taskName == v : e.task == v) {
						n = e.taskName;
						return false;
					}
					return true;
				});

				if (queueStatus > 0) {
					queuedTasks = queuedTasks.filter(function (e) {
						if (queueStatus == 1 && e.taskParent == n) {
							tasks.push(e);
						}
						return e.taskParent != n;
					});
				}
			}

			isRunning = !!(!tasks.length || (stopTasks() && false));
			return self;
		};

		/**
		 * A generator is a pair task/condition. The task will be executed
		 * while it returns true.
		 * When it returns false, the condition will be tested. If
		 * the condition returns true, the task will be executed
		 * again at the next process iteration. If not, the generator
		 * is removed.
		 * If several generators are present, they will be executed one
		 * by one: When the first stops, the second will start, etc. When
		 * they are all ended, then the conditions will be tested to know
		 * which generators have to be started again.
		 * @param {string} id                     The generators ID.
		 * @param {function(): boolean} task      The generator's task.
		 * @param {function(): boolean} condition The generator's condition.
		 * @return {sigma.chronos} Returns itself.
		 */
		function addGenerator(id, task, condition) {
			if (generators[id] != undefined) {
				return self;
			}

			generators[id] = {
				task: task,
				condition: condition
			};

			getGeneratorsCount(true) == 0 && startGenerators();
			return self;
		};

		/**
		 * Removes a generator. It means that the task will continue being eecuted
		 * until it returns false, but then the
		 * condition will not be tested.
		 * @param  {string} id The generator's ID.
		 * @return {sigma.chronos} Returns itself.
		 */
		function removeGenerator(id) {
			if (generators[id]) {
				generators[id].on = false;
				generators[id].del = true;
			}
			return self;
		};

		/**
		 * Returns the number of generators.
		 * @private
		 * @param  {boolean} running If true, returns the number of active
		 *                          generators instead.
		 * @return {sigma.chronos} Returns itself.
		 */
		function getGeneratorsCount(running) {
			return running ?
			  Object.keys(generators).filter(function (id) {
			  	return !!generators[id].on;
			  }).length :
			  Object.keys(generators).length;
		};

		/**
		 * Returns the array of the generators IDs.
		 * @return {array.<string>} The array of IDs.
		 */
		function getGeneratorsIDs() {
			return Object.keys(generators);
		}

		/**
		 * startGenerators is the method that manages which generator
		 * is the next to start when another one stops. It will dispatch
		 * a "stopgenerators" event if there is no more generator to start,
		 * and a "startgenerators" event else.
		 * @return {sigma.chronos} Returns itself.
		 */
		function startGenerators() {
			if (!Object.keys(generators).length) {
				self.dispatch('stopgenerators');
			} else {
				self.dispatch('startgenerators');

				self.unbind('killed', onTaskEnded);
				insertFrame(function () {
					for (var k in generators) {
						generators[k].on = true;
						addTask(
						  generators[k].task,
						  k,
						  false
						);
					}
				});

				self.bind('killed', onTaskEnded).runTasks();
			}

			return self;
		};

		/**
		 * A callback triggered everytime the task of a generator stops, that will
		 * test the related generator's condition, and see if there is still any
		 * generator to start.
		 * @private
		 * @param  {Object} e The sigma.chronos "killed" event.
		 */
		function onTaskEnded(e) {
			if (generators[e['content'].taskName] != undefined) {
				if (generators[e['content'].taskName].del ||
					!generators[e['content'].taskName].condition()) {
					delete generators[e['content'].taskName];
				} else {
					generators[e['content'].taskName].on = false;
				}

				if (getGeneratorsCount(true) == 0) {
					startGenerators();
				}
			}
		};

		/**
		 * Either set or returns the fpsReq property. This property determines
		 * the number of frames that should be inserted per second.
		 * @param  {?number} v The frequency asked.
		 * @return {(Chronos|number)} Returns the frequency if v is undefined, and
		 *                          itself else.
		 */
		function frequency(v) {
			if (v != undefined) {
				fpsReq = Math.abs(1 * v);
				frameTime = 1000 / fpsReq;
				framesCount = 0;
				return self;
			} else {
				return fpsReq;
			}
		};

		/**
		 * Returns the actual average number of frames that are inserted per
		 * second.
		 * @return {number} The actual average FPS.
		 */
		function getFPS() {
			if (isRunning) {
				lastFPS =
				  Math.round(
					framesCount /
					((new Date()).getTime() - currentTime) *
					10000
				  ) / 10;
			}

			return lastFPS;
		};

		/**
		 * Returns the number of tasks.
		 * @return {number} The number of tasks.
		 */
		function getTasksCount() {
			return tasks.length;
		}

		/**
		 * Returns the number of queued tasks.
		 * @return {number} The number of queued tasks.
		 */
		function getQueuedTasksCount() {
			return queuedTasks.length;
		}

		/**
		 * Returns how long sigma.chronos has active tasks running
		 * without interuption for, in ms.
		 * @return {number} The time chronos is running without interuption for.
		 */
		function getExecutionTime() {
			return startTime - currentTime;
		}

		this.frequency = frequency;

		this.runTasks = runTasks;
		this.stopTasks = stopTasks;
		this.insertFrame = insertFrame;

		this.addTask = addTask;
		this.queueTask = queueTask;
		this.removeTask = removeTask;

		this.addGenerator = addGenerator;
		this.removeGenerator = removeGenerator;
		this.startGenerators = startGenerators;
		this.getGeneratorsIDs = getGeneratorsIDs;

		this.getFPS = getFPS;
		this.getTasksCount = getTasksCount;
		this.getQueuedTasksCount = getQueuedTasksCount;
		this.getExecutionTime = getExecutionTime;

		return this;
	})();

	sigma.debugMode = 0;

	sigma.log = function () {
		if (sigma.debugMode == 1) {
			for (var k in arguments) {
				console.log(arguments[k]);
			}
		} else if (sigma.debugMode > 1) {
			for (var k in arguments) {
				throw new Error(arguments[k]);
			}
		}

		return sigma;
	};

	sigma.easing = {
		linear: {},
		quadratic: {}
	};

	sigma.easing.linear.easenone = function (k) {
		return k;
	};

	sigma.easing.quadratic.easein = function (k) {
		return k * k;
	};

	sigma.easing.quadratic.easeout = function (k) {
		return -k * (k - 2);
	};

	sigma.easing.quadratic.easeinout = function (k) {
		if ((k *= 2) < 1) return 0.5 * k * k;
		return -0.5 * (--k * (k - 2) - 1);
	};

	sigma.tools.drawRoundRect = function (ctx, x, y, w, h, ellipse, corners) {
		var e = ellipse ? ellipse : 0;
		var c = corners ? corners : [];
		c = ((typeof c) == 'string') ? c.split(' ') : c;

		var tl = e && (c.indexOf('topleft') >= 0 ||
					   c.indexOf('top') >= 0 ||
					   c.indexOf('left') >= 0);
		var tr = e && (c.indexOf('topright') >= 0 ||
					   c.indexOf('top') >= 0 ||
					   c.indexOf('right') >= 0);
		var bl = e && (c.indexOf('bottomleft') >= 0 ||
					   c.indexOf('bottom') >= 0 ||
					   c.indexOf('left') >= 0);
		var br = e && (c.indexOf('bottomright') >= 0 ||
					   c.indexOf('bottom') >= 0 ||
					   c.indexOf('right') >= 0);

		ctx.moveTo(x, y + e);

		if (tl) {
			ctx.arcTo(x, y, x + e, y, e);
		} else {
			ctx.lineTo(x, y);
		}

		if (tr) {
			ctx.lineTo(x + w - e, y);
			ctx.arcTo(x + w, y, x + w, y + e, e);
		} else {
			ctx.lineTo(x + w, y);
		}

		if (br) {
			ctx.lineTo(x + w, y + h - e);
			ctx.arcTo(x + w, y + h, x + w - e, y + h, e);
		} else {
			ctx.lineTo(x + w, y + h);
		}

		if (bl) {
			ctx.lineTo(x + e, y + h);
			ctx.arcTo(x, y + h, x, y + h - e, e);
		} else {
			ctx.lineTo(x, y + h);
		}

		ctx.lineTo(x, y + e);
	};

	/**
	 * Draws a filled arrowhead shape to the corresponding canvas.
	 * @param  {CanvasRenderingContext2D} ctx  The context within which to draw the
	 *                                         arrowhead.
	 * @param  {number} x0                     The x-coordinate of the tip of the
	 *                                         arrowhead.
	 * @param  {number} y0                     The y-coordinate of the tip of the
	 *                                         arrowhead.
	 * @param  {number} size                   The length of the arrowhead.
	 * @param  {number} rotationAngle          The angle of rotation of the
	 *                                         arrowhead, in degrees.
	 * @return {undefined}
	 */
	sigma.tools.drawArrowhead = function (ctx, x0, y0, size, rotationAngle) {
		// Angle between one side of arrowhead and shaft:
		var ARROW_SHARPNESS = 22;

		ctx.beginPath();

		ctx.moveTo(x0, y0);

		// (Math.PI / 180) === 0.017453292519943295
		var coef = 0.017453292519943295;
		var x1 = x0 + Math.cos(coef * (ARROW_SHARPNESS + rotationAngle)) * size;
		var y1 = y0 + Math.sin(coef * (ARROW_SHARPNESS + rotationAngle)) * size;
		var x2 = x0 + Math.cos(coef * (rotationAngle - ARROW_SHARPNESS)) * size;
		var y2 = y0 + Math.sin(coef * (rotationAngle - ARROW_SHARPNESS)) * size;

		ctx.lineTo(x1, y1);
		ctx.quadraticCurveTo((x0 + x1 + x2) / 3, (y0 + y1 + y2) / 3, x2, y2);
		ctx.lineTo(x0, y0);
		ctx.fill();
	};

	sigma.tools.getRGB = function (s, asArray) {
		s = s.toString();
		var res = {
			'r': 0,
			'g': 0,
			'b': 0
		};

		if (s.length >= 3) {
			if (s.charAt(0) == '#') {
				var l = s.length - 1;
				if (l == 6) {
					res = {
						'r': parseInt(s.charAt(1) + s.charAt(2), 16),
						'g': parseInt(s.charAt(3) + s.charAt(4), 16),
						'b': parseInt(s.charAt(5) + s.charAt(5), 16)
					};
				} else if (l == 3) {
					res = {
						'r': parseInt(s.charAt(1) + s.charAt(1), 16),
						'g': parseInt(s.charAt(2) + s.charAt(2), 16),
						'b': parseInt(s.charAt(3) + s.charAt(3), 16)
					};
				}
			}
		}

		if (asArray) {
			res = [
			  res['r'],
			  res['g'],
			  res['b']
			];
		}

		return res;
	};

	sigma.tools.rgbToHex = function (R, G, B) {
		return sigma.tools.toHex(R) + sigma.tools.toHex(G) + sigma.tools.toHex(B);
	};

	sigma.tools.toHex = function (n) {
		n = parseInt(n, 10);

		if (isNaN(n)) {
			return '00';
		}
		n = Math.max(0, Math.min(n, 255));
		return '0123456789ABCDEF'.charAt((n - n % 16) / 16) +
			   '0123456789ABCDEF'.charAt(n % 16);
	};

	/**
	 * Provides the angle of incidence of the end point of a line or quadratic
	 * curve, in degrees.
	 * @param  {number} x1  The x-coordinate of the start point of the line or
	 *                      control point of the quadratic curve.
	 * @param  {number} y1  The y-coordinate of the start point of the line or
	 *                      control point of the quadratic curve.
	 * @param  {number} x2  The x-coordinate of the line or quadratic curve end
	 *                      point.
	 * @param  {number} y2  The y-coordinate of the line or quadratic curve end
	 *                      point.
	 * @return {number}     Returns the angle of incidence of the end point of the
	 *                      line or quadratic curve cooresponding to the coordinate
	 *                      parms, in degrees.
	 */
	sigma.tools.getIncidenceAngle = function (x1, y1, x2, y2) {
		return (
		  (x1 <= x2 ? 180 : 0) +
		  Math.atan(((y2 - y1) / (x2 - x1))) * 180 / Math.PI
		);
	};
	sigma.publicPrototype = SigmaPublic.prototype;
})();

